FEAT: Migrate Collection and User Auth endpoints to FastEndpoints (API-First), implement MudDialog based Users CRUD (MVVM) and update AGENTS.md guidelines
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 10s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 1m5s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 10s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 1m5s
This commit is contained in:
@@ -1,158 +1,233 @@
|
||||
using FastEndpoints;
|
||||
using QuantEngine.Core.Interfaces;
|
||||
using QuantEngine.Application.Services;
|
||||
|
||||
namespace QuantEngine.Web.Endpoints;
|
||||
|
||||
public static class CollectionEndpoints
|
||||
public class GetCollectionStateEndpoint : EndpointWithoutRequest<CollectionDashboardStateRecord>
|
||||
{
|
||||
public static void MapCollectionEndpoints(this WebApplication app)
|
||||
private readonly ICollectionRepository _repo;
|
||||
|
||||
public GetCollectionStateEndpoint(ICollectionRepository repo)
|
||||
{
|
||||
var group = app.MapGroup("/api/collection")
|
||||
.WithName("Collection");
|
||||
|
||||
group.MapGet("/state", GetCollectionState)
|
||||
.WithName("GetCollectionState")
|
||||
.Produces(200)
|
||||
.Produces(500);
|
||||
|
||||
group.MapGet("/runs", GetRecentRuns)
|
||||
.WithName("GetRecentRuns")
|
||||
.Produces(200)
|
||||
.Produces(500);
|
||||
|
||||
group.MapGet("/runs/{runId}/snapshots", GetRunSnapshots)
|
||||
.WithName("GetRunSnapshots")
|
||||
.Produces(200)
|
||||
.Produces(404)
|
||||
.Produces(500);
|
||||
|
||||
group.MapGet("/runs/{runId}/errors", GetRunErrors)
|
||||
.WithName("GetRunErrors")
|
||||
.Produces(200)
|
||||
.Produces(404)
|
||||
.Produces(500);
|
||||
|
||||
group.MapGet("/latest/{ticker}", GetLatestSnapshotsForTicker)
|
||||
.WithName("GetLatestSnapshotsForTicker")
|
||||
.Produces(200)
|
||||
.Produces(500);
|
||||
|
||||
group.MapPost("/run", StartCollectionRun)
|
||||
.WithName("StartCollectionRun")
|
||||
.Produces(202)
|
||||
.Produces(500);
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetCollectionState(ICollectionRepository repo)
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/collection/state");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces<CollectionDashboardStateRecord>(200)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = await repo.GetDashboardStateAsync();
|
||||
return Results.Ok(state);
|
||||
var state = await _repo.GetDashboardStateAsync();
|
||||
await SendOkAsync(state, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRecentRuns(ICollectionRepository repo, int limit = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runs = await repo.GetRecentRunsAsync(limit);
|
||||
return Results.Ok(new { runs, count = runs.Count });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRunSnapshots(string runId, ICollectionRepository repo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshots = await repo.GetRunSnapshotsAsync(runId);
|
||||
return Results.Ok(new { runId, snapshots, count = snapshots.Count });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRunErrors(string runId, ICollectionRepository repo, int limit = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errors = await repo.GetRunErrorsAsync(runId, limit);
|
||||
return Results.Ok(new { runId, errors, count = errors.Count });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetLatestSnapshotsForTicker(string ticker, ICollectionRepository repo, int limit = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshots = await repo.GetLatestSnapshotsForTickerAsync(ticker, limit);
|
||||
return Results.Ok(new { ticker, snapshots, count = snapshots.Count });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> StartCollectionRun(
|
||||
DataCollectionService collectionService,
|
||||
HttpRequest request,
|
||||
ILogger<Program> logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runId = Guid.NewGuid().ToString("N");
|
||||
var now = DateTime.UtcNow.ToString("o");
|
||||
|
||||
var body = await request.ReadFromJsonAsync<CollectionRunRequest>();
|
||||
var account = body?.Account ?? "real";
|
||||
var tickers = body?.Tickers ?? new List<string> { "005930", "000660" };
|
||||
|
||||
// Trigger async collection (fire-and-forget)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await collectionService.RunCollectionAsync(runId, account, tickers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Collection run {RunId} failed", runId);
|
||||
}
|
||||
});
|
||||
|
||||
return Results.Accepted($"/api/collection/runs/{runId}", new
|
||||
{
|
||||
runId,
|
||||
status = "running",
|
||||
startedAt = now,
|
||||
tickerCount = tickers.Count
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to start collection run");
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
private class CollectionRunRequest
|
||||
{
|
||||
public string? Account { get; set; }
|
||||
public List<string>? Tickers { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class GetRecentRunsRequest
|
||||
{
|
||||
public int Limit { get; set; } = 20;
|
||||
}
|
||||
|
||||
public class GetRecentRunsResponse
|
||||
{
|
||||
public List<CollectionRunRecord> Runs { get; set; } = new();
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class GetRecentRunsEndpoint : Endpoint<GetRecentRunsRequest, GetRecentRunsResponse>
|
||||
{
|
||||
private readonly ICollectionRepository _repo;
|
||||
|
||||
public GetRecentRunsEndpoint(ICollectionRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/collection/runs");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces<GetRecentRunsResponse>(200)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetRecentRunsRequest req, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runs = await _repo.GetRecentRunsAsync(req.Limit);
|
||||
await SendOkAsync(new GetRecentRunsResponse { Runs = runs, Count = runs.Count }, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GetRunSnapshotsRequest
|
||||
{
|
||||
public string RunId { get; set; } = "";
|
||||
}
|
||||
|
||||
public class GetRunSnapshotsResponse
|
||||
{
|
||||
public string RunId { get; set; } = "";
|
||||
public List<CollectionSnapshotRecord> Snapshots { get; set; } = new();
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class GetRunSnapshotsEndpoint : Endpoint<GetRunSnapshotsRequest, GetRunSnapshotsResponse>
|
||||
{
|
||||
private readonly ICollectionRepository _repo;
|
||||
|
||||
public GetRunSnapshotsEndpoint(ICollectionRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/collection/runs/{RunId}/snapshots");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces<GetRunSnapshotsResponse>(200)
|
||||
.Produces(404)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetRunSnapshotsRequest req, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshots = await _repo.GetRunSnapshotsAsync(req.RunId);
|
||||
await SendOkAsync(new GetRunSnapshotsResponse { RunId = req.RunId, Snapshots = snapshots, Count = snapshots.Count }, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GetRunErrorsRequest
|
||||
{
|
||||
public string RunId { get; set; } = "";
|
||||
public int Limit { get; set; } = 50;
|
||||
}
|
||||
|
||||
public class GetRunErrorsResponse
|
||||
{
|
||||
public string RunId { get; set; } = "";
|
||||
public List<CollectionErrorRecord> Errors { get; set; } = new();
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class GetRunErrorsEndpoint : Endpoint<GetRunErrorsRequest, GetRunErrorsResponse>
|
||||
{
|
||||
private readonly ICollectionRepository _repo;
|
||||
|
||||
public GetRunErrorsEndpoint(ICollectionRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/collection/runs/{RunId}/errors");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces<GetRunErrorsResponse>(200)
|
||||
.Produces(404)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetRunErrorsRequest req, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errors = await _repo.GetRunErrorsAsync(req.RunId, req.Limit);
|
||||
await SendOkAsync(new GetRunErrorsResponse { RunId = req.RunId, Errors = errors, Count = errors.Count }, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GetLatestSnapshotsRequest
|
||||
{
|
||||
public string Ticker { get; set; } = "";
|
||||
public int Limit { get; set; } = 10;
|
||||
}
|
||||
|
||||
public class GetLatestSnapshotsResponse
|
||||
{
|
||||
public string Ticker { get; set; } = "";
|
||||
public List<CollectionSnapshotRecord> Snapshots { get; set; } = new();
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class GetLatestSnapshotsEndpoint : Endpoint<GetLatestSnapshotsRequest, GetLatestSnapshotsResponse>
|
||||
{
|
||||
private readonly ICollectionRepository _repo;
|
||||
|
||||
public GetLatestSnapshotsEndpoint(ICollectionRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/collection/latest/{Ticker}");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces<GetLatestSnapshotsResponse>(200)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetLatestSnapshotsRequest req, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshots = await _repo.GetLatestSnapshotsForTickerAsync(req.Ticker, req.Limit);
|
||||
await SendOkAsync(new GetLatestSnapshotsResponse { Ticker = req.Ticker, Snapshots = snapshots, Count = snapshots.Count }, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StartCollectionRunEndpoint : EndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/collection/run");
|
||||
AllowAnonymous();
|
||||
Description(d => d
|
||||
.Produces(202)
|
||||
.Produces(500));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
// Return 202 Accepted status code via generic status code handler
|
||||
await SendResultAsync(Microsoft.AspNetCore.Http.Results.Accepted());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user