-
Notifications
You must be signed in to change notification settings - Fork 1
VSA
IOF edited this page Nov 28, 2025
·
1 revision
public record CreateAccountDto(
AccountType Type,
string Currency,
decimal Balance,
decimal? InterestRate
);
public static class CreateAccountEndpoint
{
public static WebApplication MapEndpoint(this WebApplication app)
{
app.MapPost("/account", HandleEndpoint)
.WithTags("Аккаунты")
.WithName("CreateAccount")
.WithSummary("Создание нового счета")
.WithDescription("Возвращает объект счета Account")
.Produces<Account>(StatusCodes.Status201Created)
.Produces<MbResult<Account>>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization();
return app;
}
public static async Task<IResult> HandleEndpoint(
CreateAccountDto createAccountDto,
IMediator mediator,
ClaimsPrincipal user
)
{
var ownerId = user.GetOwnerIdFromClaims();
if (ownerId == Guid.Empty)
return Results.Unauthorized();
var request = new CreateAccountRequest(createAccountDto, ownerId);
var response = await mediator.Send(request);
return response.IsSuccess
? Results.CreatedAtRoute(
"GetAccount",
new { accountId = response.Value.Id },
response.Value.ToDto()
)
// ? Results.Created($"/account/{response.Value.Id}", response.Value)
: Results.BadRequest(response);
}
}
public class CreateAccountHandler(
IAccountRepository repo,
ILogger<CreateAccountHandler> logger,
ModuleBankAppContext db
) : IRequestHandler<CreateAccountRequest, MbResult<Account>>
{
public async Task<MbResult<Account>> Handle(CreateAccountRequest request, CancellationToken ct)
{
// ReSharper disable once RedundantEmptyObjectCreationArgumentList
var account = new Account()
{
Type = request.CreateAccountDto.Type,
Balance = request.CreateAccountDto.Balance,
InterestRate = request.CreateAccountDto.InterestRate,
Currency = request.CreateAccountDto.Currency,
OwnerId = (Guid)request.ClaimsId!,
};
var @event = new AccountOpened(
Guid.NewGuid(),
account.CreatedAt,
account.Id,
account.OwnerId,
account.Currency,
account.Type
);
await using var tx = await db.Database.BeginTransactionAsync(ct);
var savedAccount = await repo.CreateAccount(account);
logger.LogInformation(
"\n Creating account {Id} for user {OwnerId} \n",
savedAccount.Id,
savedAccount.OwnerId
);
await db.Outbox.AddAsync(
new OutboxMessage
{
Type = nameof(AccountOpened),
Payload = JsonSerializer.Serialize(@event),
Status = OutboxStatus.Pending,
},
ct
);
await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
return MbResult<Account>.Success(savedAccount);
}
}
public record CreateAccountRequest(CreateAccountDto CreateAccountDto, Guid? ClaimsId)
: IRequest<MbResult<Account>>;
public class CreateAccountValidator : AbstractValidator<CreateAccountRequest>
{
public CreateAccountValidator(ICurrencyService currencyService)
{
ClassLevelCascadeMode = CascadeMode.Continue;
RuleLevelCascadeMode = CascadeMode.Stop;
RuleFor(x => x.CreateAccountDto.Type)
.Must(t => t is AccountType.Credit or AccountType.Deposit or AccountType.Checking)
.WithMessage("Тип счёта должен быть 'Credit', 'Deposit' или 'Checking'");
RuleFor(x => x.CreateAccountDto.Currency)
.NotEmpty()
.WithMessage("Требуется код валюты.")
.Length(3)
.WithMessage("Код валюты должен состоять из 3 символов.")
.Must(currencyService.IsValidCurrencyCode)
.WithMessage("Неверный код валюты (ISO 4217).");
// Депозит/расчётный — баланс >= 0
When(
x => x.CreateAccountDto.Type is AccountType.Deposit or AccountType.Checking,
() =>
{
RuleFor(x => x.CreateAccountDto.Balance)
.NotNull()
.GreaterThanOrEqualTo(0)
.WithMessage("Баланс не может быть отрицательным для выбранного типа счёта.");
}
);
// Кредит — баланс <= 0 (если нужен кредитный лимит, вынесите его отдельно)
When(
x => x.CreateAccountDto.Type is AccountType.Credit,
() =>
{
RuleFor(x => x.CreateAccountDto.Balance)
.NotNull()
.LessThanOrEqualTo(0)
.WithMessage("Для кредитного счёта баланс должен быть неположительным (≤ 0).");
}
);
// Ставка обязательна для Deposit/Credit
When(
x => x.CreateAccountDto.Type is AccountType.Deposit or AccountType.Credit,
() =>
{
RuleFor(x => x.CreateAccountDto.InterestRate)
.NotNull()
.GreaterThan(0)
.WithMessage(
"Для выбранного типа счета требуется положительная процентная ставка."
);
}
);
RuleFor(x => x.CreateAccountDto.Type)
.Must(type => Enum.IsDefined(type) && type != AccountType.None)
.WithMessage("Недопустимый тип счёта.");
}
}