using System.Text.RegularExpressions; using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Studio.IssueDonationCode; internal sealed class Handler(IAppDbContext db) : ICommandHandler> { public async Task> Handle(Command request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Code)) { return Result.Failure(Error.Problem("DonationCode.Required", "후원 코드를 입력해 주세요.")); } var trimmed = request.Code.Trim(); if (!Regex.IsMatch(trimmed, "^[A-Za-z0-9]{4,7}$")) { return Result.Failure(Error.Problem("DonationCode.InvalidFormat", "후원 코드는 4~7자 영문/숫자만 가능합니다.")); } var channel = await db.Channel.FirstOrDefaultAsync(c => c.MemberID == request.MemberID && c.IsActive, ct); if (channel is null) { return Result.Failure(Error.NotFound("Channel.NotFound", "활성 채널을 찾을 수 없습니다.")); } if (channel.DonationCode is not null) { return Result.Failure(Error.Conflict("DonationCode.AlreadyIssued", "이미 후원 코드가 발급되어 변경할 수 없습니다.")); } var normalized = trimmed.ToUpperInvariant(); var duplicate = await db.Channel.AsNoTracking().AnyAsync(c => c.DonationCode == normalized, ct); if (duplicate) { return Result.Failure(Error.Conflict("DonationCode.Duplicate", "이미 사용 중인 후원 코드입니다. 다른 코드를 시도해 주세요.")); } try { channel.IssueDonationCode(normalized); await db.SaveChangesAsync(ct); } catch (DbUpdateException) { return Result.Failure(Error.Conflict("DonationCode.Duplicate", "이미 사용 중인 후원 코드입니다. 다른 코드를 시도해 주세요.")); } return Result.Success(new Response(channel.DonationCode!)); } }