V13-FE-006: consolidate approved UI and contract hardening
This commit is contained in:
@@ -59,7 +59,7 @@ public class QueryAuditEventsEndpoint : Endpoint<QueryAuditEventsRequest, QueryA
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/audit/events");
|
||||
AllowAnonymous(); // RBAC enforced at handler level (Compliance Officer role)
|
||||
Roles("Compliance");
|
||||
Summary(x =>
|
||||
{
|
||||
x.Summary = "Query Audit Events";
|
||||
|
||||
@@ -39,7 +39,7 @@ public class SubmitGdprRequestEndpoint : Endpoint<SubmitGdprRequestDto, GdprRequ
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/compliance/gdpr-request");
|
||||
AllowAnonymous(); // RBAC enforced at handler level (Data Admin/Compliance Officer role)
|
||||
Roles("DataAdmin", "Compliance");
|
||||
Summary(x =>
|
||||
{
|
||||
x.Summary = "Submit GDPR Request";
|
||||
|
||||
@@ -22,7 +22,7 @@ public class CreateApprovalEndpoint : EndpointWithoutRequest<CreateApprovalRespo
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/approvals");
|
||||
AllowAnonymous();
|
||||
Roles("Maker");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
@@ -51,7 +51,7 @@ public class GetApprovalsEndpoint : Endpoint<GetApprovalsRequest, GetApprovalsRe
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/approvals");
|
||||
AllowAnonymous();
|
||||
Roles("Maker", "Checker", "SRE");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetApprovalsRequest req, CancellationToken ct)
|
||||
@@ -76,7 +76,7 @@ public class ProposeForReviewEndpoint : EndpointWithoutRequest<ProposeForReviewR
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/approvals/{id}/propose");
|
||||
AllowAnonymous();
|
||||
Roles("Maker");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
@@ -111,7 +111,7 @@ public class ActivateApprovalEndpoint : EndpointWithoutRequest<ActivateApprovalR
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/approvals/{id}/activate");
|
||||
AllowAnonymous();
|
||||
Roles("SRE");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
@@ -140,7 +140,7 @@ public class GetApprovalByIdEndpoint : EndpointWithoutRequest<ApprovalDetailResp
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/approvals/{id}");
|
||||
AllowAnonymous();
|
||||
Roles("Maker", "Checker", "SRE");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
@@ -183,7 +183,7 @@ public class ApproveApprovalEndpoint : Endpoint<ApproveApprovalRequest, ApproveA
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/approvals/{id}/approve");
|
||||
AllowAnonymous();
|
||||
Roles("Checker");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ApproveApprovalRequest req, CancellationToken ct)
|
||||
|
||||
@@ -6,10 +6,12 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FastEndpoints;
|
||||
using KArtSell.BuildingBlocks.Time;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
/// <summary>
|
||||
/// GET /reconciliation/holdings - Returns current portfolio holdings
|
||||
/// </summary>
|
||||
[DontRegister]
|
||||
public class GetHoldingsEndpoint : EndpointWithoutRequest<GetHoldingsResponse>
|
||||
{
|
||||
private readonly IReconciliationRepository _repository;
|
||||
@@ -74,6 +76,7 @@ public class HoldingDto
|
||||
/// <summary>
|
||||
/// GET /reconciliation/mismatches - Returns flagged discrepancies
|
||||
/// </summary>
|
||||
[DontRegister]
|
||||
public class GetMismatchesEndpoint : EndpointWithoutRequest<GetMismatchesResponse>
|
||||
{
|
||||
private readonly IReconciliationRepository _repository;
|
||||
@@ -148,6 +151,7 @@ public class MismatchDto
|
||||
/// <summary>
|
||||
/// POST /reconciliation/reconcile-trade - Trigger trade reconciliation
|
||||
/// </summary>
|
||||
[DontRegister]
|
||||
public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
|
||||
{
|
||||
private readonly ReconcileTradeHandler _handler;
|
||||
@@ -165,6 +169,17 @@ public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
|
||||
|
||||
public override async Task HandleAsync(ReconcileTradeRequest request, CancellationToken ct)
|
||||
{
|
||||
if (!ReconcileTradeRequestContract.HasIdempotencyKey(request))
|
||||
{
|
||||
await Send.ResponseAsync(new
|
||||
{
|
||||
type = "https://httpstatuses.com/400",
|
||||
title = "Invalid reconciliation request",
|
||||
detail = "Idempotency-Key is required for replay-safe reconciliation."
|
||||
}, StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var command = new ReconcileTradeCommand
|
||||
{
|
||||
TradeId = request.TradeId,
|
||||
@@ -186,6 +201,12 @@ public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReconcileTradeRequestContract
|
||||
{
|
||||
public static bool HasIdempotencyKey(ReconcileTradeRequest request) =>
|
||||
request is not null && !string.IsNullOrWhiteSpace(request.IdempotencyKey);
|
||||
}
|
||||
|
||||
public class ReconcileTradeRequest
|
||||
{
|
||||
public Guid TradeId { get; set; }
|
||||
@@ -204,6 +225,7 @@ public class ReconcileTradeRequest
|
||||
/// <summary>
|
||||
/// GET /reconciliation/report/daily - Returns daily reconciliation report
|
||||
/// </summary>
|
||||
[DontRegister]
|
||||
public class GetDailyReportEndpoint : EndpointWithoutRequest<ReconciliationReportDto>
|
||||
{
|
||||
private readonly ReconciliationEngine _engine;
|
||||
|
||||
+5
-1
@@ -51,6 +51,10 @@ public class ReconcileTradeHandler
|
||||
public async Task HandleAsync(ReconcileTradeCommand command)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
if (string.IsNullOrWhiteSpace(command.IdempotencyKey))
|
||||
{
|
||||
throw new ArgumentException("IdempotencyKey is required for replay-safe reconciliation.", nameof(command));
|
||||
}
|
||||
|
||||
// DEBT-018: share one connection/transaction across the engine's holding/log writes and
|
||||
// the outbox event(s) below, instead of the engine writing on its own connection and the
|
||||
@@ -93,7 +97,7 @@ public class ReconcileTradeHandler
|
||||
: null,
|
||||
ReconciliationTimestamp = _clock.UtcNow.UtcDateTime,
|
||||
CorrelationId = command.CorrelationId,
|
||||
IdempotencyKey = command.IdempotencyKey ?? Guid.NewGuid().ToString()
|
||||
IdempotencyKey = command.IdempotencyKey
|
||||
};
|
||||
|
||||
await PublishAsync(transaction, "TradeReconciled", @event, command.CorrelationId, CancellationToken.None);
|
||||
|
||||
@@ -18,7 +18,6 @@ public class CreateSellDecisionEndpoint : Endpoint<CreateSellDecisionRequest, Cr
|
||||
{
|
||||
Post("/sell-decisions");
|
||||
Roles("Maker");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CreateSellDecisionRequest req, CancellationToken ct)
|
||||
@@ -45,7 +44,6 @@ public class ListSellDecisionsEndpoint : EndpointWithoutRequest<ListSellDecision
|
||||
{
|
||||
Get("/sell-decisions");
|
||||
Roles("Quant", "Maker", "Checker");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
@@ -112,7 +110,6 @@ public class ExecuteSellDecisionEndpoint : Endpoint<ExecuteSellDecisionRequest,
|
||||
{
|
||||
Post("/sell-decisions/{id}/execute");
|
||||
Roles("Maker", "Checker");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ExecuteSellDecisionRequest req, CancellationToken ct)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace KArtSell.Modules.ModelOperations.TradeExecution;
|
||||
|
||||
[DontRegister]
|
||||
public class CreateTradeEndpoint : Endpoint<CreateTradeRequest, CreateTradeResponse>
|
||||
{
|
||||
private readonly SubmitTradeHandler _handler;
|
||||
@@ -53,6 +54,7 @@ public class CreateTradeEndpoint : Endpoint<CreateTradeRequest, CreateTradeRespo
|
||||
}
|
||||
}
|
||||
|
||||
[DontRegister]
|
||||
public class ListTradesEndpoint : Endpoint<EmptyRequest, ListTradesResponse>
|
||||
{
|
||||
private readonly ITradeSql _sql;
|
||||
|
||||
Reference in New Issue
Block a user