V13-FE-011: finalize search list layout slice
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
using Dapper;using FastEndpoints;using Npgsql;using Kbx.Shared.Runtime;using Shared.Problems;
|
||||
namespace Kbx.Modules.Common.Integrations.Attempts.Get;
|
||||
public sealed record Response(Guid Id,string IntegrationId,string MessageId,string AggregateType,string AggregateId,string State,int AttemptNo,string? FailureCode,string? Detail,DateTimeOffset StartedAt,DateTimeOffset? CompletedAt,DateTimeOffset? NextRetryAt,string CorrelationId);
|
||||
public sealed class Endpoint(NpgsqlDataSource dataSource):EndpointWithoutRequest<object>
|
||||
{
|
||||
public override void Configure(){Get("/api/integrations/attempts/{attemptId:guid}");Permissions("common.integration.read");}
|
||||
public override async Task HandleAsync(CancellationToken ct){var attemptId=Route<Guid>("attemptId");await using var c=await dataSource.OpenConnectionAsync(ct);const string sql="""select id, integration_id as IntegrationId, message_id::text as MessageId, aggregate_type as AggregateType, aggregate_id as AggregateId, state, attempt_no as AttemptNo, failure_code as FailureCode, detail, started_at as StartedAt, completed_at as CompletedAt, next_retry_at as NextRetryAt, correlation_id as CorrelationId from kbx.integration_attempts where id=@Id and tenant_id=@TenantId""";var row=await c.QuerySingleOrDefaultAsync<Response>(new CommandDefinition(sql,new{Id=attemptId,TenantId=RuntimeIdentity.TenantId(User)},cancellationToken:ct));if(row is null){await Send.ResponseAsync(KbxNotFoundProblem.Create("INTEGRATION_ATTEMPT_NOT_FOUND","연계 이력을 찾을 수 없습니다."),404,cancellation:ct);return;}await Send.OkAsync(row,ct);}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using System.Text.Json;
|
||||
using Dapper;
|
||||
using FastEndpoints;
|
||||
using Npgsql;
|
||||
using Kbx.Shared.Runtime;
|
||||
using Shared.Problems;
|
||||
|
||||
namespace Kbx.Modules.Common.Integrations.Attempts.Retry;
|
||||
|
||||
public sealed record Request(string? IdempotencyKey = null);
|
||||
public sealed record Response(Guid AttemptId,string State,string Message);
|
||||
|
||||
public sealed class Endpoint(NpgsqlDataSource dataSource):Endpoint<Request,object>
|
||||
{
|
||||
private const string OperationId = "common.integrations.retryAttempt";
|
||||
public override void Configure(){Post("/api/integrations/attempts/{attemptId:guid}/retry");Permissions("common.integration.retry");}
|
||||
|
||||
public override async Task HandleAsync(Request req,CancellationToken ct)
|
||||
{
|
||||
var attemptId=Route<Guid>("attemptId");
|
||||
var key=HttpContext.Request.Headers["Idempotency-Key"].FirstOrDefault()??req.IdempotencyKey;
|
||||
if(string.IsNullOrWhiteSpace(key)){
|
||||
await Send.ResponseAsync(KbxValidationProblem.Create(new KbxValidationError(null,null,"IDEMPOTENCY_KEY_REQUIRED","안전한 재처리를 위해 Idempotency-Key가 필요합니다.")),400,cancellation:ct);return;
|
||||
}
|
||||
await using var c=await dataSource.OpenConnectionAsync(ct);
|
||||
await using var tx=await c.BeginTransactionAsync(ct);
|
||||
var replay=await c.QuerySingleOrDefaultAsync<string?>(new CommandDefinition(
|
||||
"select response_json::text from kbx.command_receipts where operation_id=@OperationId and idempotency_key=@Key",
|
||||
new { OperationId, Key=key },tx,cancellationToken:ct));
|
||||
if(replay is not null){await tx.RollbackAsync(ct);await Send.OkAsync(JsonSerializer.Deserialize<Response>(replay)!,ct);return;}
|
||||
|
||||
const string sql="""
|
||||
update kbx.integration_attempts
|
||||
set state='retrying', next_retry_at=now(), manual_retry_key=@Key
|
||||
where id=@Id and tenant_id=@TenantId and state='failed'
|
||||
returning id;
|
||||
""";
|
||||
var id=await c.ExecuteScalarAsync<Guid?>(new CommandDefinition(sql,new{Id=attemptId,TenantId=RuntimeIdentity.TenantId(User),Key=key},tx,cancellationToken:ct));
|
||||
if(id is null){await tx.RollbackAsync(ct);await Send.ResponseAsync(KbxNotFoundProblem.Create("INTEGRATION_ATTEMPT_NOT_RETRYABLE","재처리할 수 있는 연계 실패를 찾지 못했습니다."),404,cancellation:ct);return;}
|
||||
|
||||
var response=new Response(id.Value,"retrying","재처리를 예약했습니다.");
|
||||
await c.ExecuteAsync(new CommandDefinition("insert into kbx.command_receipts(operation_id,idempotency_key,response_json) values(@OperationId,@Key,cast(@Json as jsonb))",
|
||||
new { OperationId, Key=key, Json=JsonSerializer.Serialize(response) },tx,cancellationToken:ct));
|
||||
await tx.CommitAsync(ct);
|
||||
await Send.OkAsync(response,ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user