Files
CMS/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs
T
masoodafar-web 2502cbbda2
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m44s
feat: integrate PYMS payment gateway, add blog/sitepage/image services, local file manager
Payment Gateway:
- Add PYMSPaymentService: IPaymentGatewayService via gRPC to PYMS microservice
- Add ZarinPalPaymentService: direct ZarinPal integration (backup)
- Register 'pyms' payment provider in DI ConfigureServices
- Add PYMS proto files (pyms_transaction.proto, pyms_public_messages.proto)
- Fix VerifyDiscountWalletCharge: pass 'OK' as status instead of Authority
- Update appsettings: PaymentProvider=pyms, sandbox mode, merchant ID

Blog System:
- Add BlogCategory, BlogPost, BlogPostImage entities and CQRS
- Add proto files and gRPC services for blog management
- Add Mapster profiles for blog responses

Content Management:
- Add SitePage entity and CQRS for static pages
- Add proto and gRPC service for site pages

Image/File Management:
- Add LocalFileManager with disk storage + base64 serving + FMS fallback
- Add ImagePathResolverInterceptor for gRPC responses
- Add ImageResolverService for explicit image resolution
- Add UploadsController for public file serving with FMS fallback
- Add PaymentCallbackController for discount order payment callbacks

Database:
- Add blog and content entity migrations
- Remove ImagePath MaxLength constraints
- Remove old FileManagementService (replaced by LocalFileManager)
2026-02-15 23:01:16 +03:30

83 lines
3.2 KiB
C#

using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Configuration;
using System.Security.Cryptography;
using System.Text;
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.CreateNewOtpToken;
public class CreateNewOtpTokenCommandHandler : IRequestHandler<CreateNewOtpTokenCommand, CreateNewOtpTokenResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly IConfiguration _cfg;
private readonly IHashService _hashService;
public CreateNewOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg, IHashService hashService)
{
_context = context;
_cfg = cfg;
_hashService = hashService;
}
const int CodeLength = 6;
const int MaxAttempts = 5; // محدودیت تلاش
static readonly TimeSpan Ttl = TimeSpan.FromMinutes(2);
static readonly TimeSpan Cooldown = TimeSpan.FromSeconds(60); // فاصله ارسال مجدد
public async Task<CreateNewOtpTokenResponseDto> Handle(CreateNewOtpTokenCommand request,
CancellationToken cancellationToken)
{
var mobile = request.Mobile.NormalizeIranMobile();
var purpose = request.Purpose?.ToLowerInvariant() ?? "signup";
// ریت‌لیمیت ساده: اگر هنوز کدی فعال و تازه داریم، اجازه نده
var now = DateTime.Now;
var lastActive = await _context.OtpTokens
.Where(o => o.Mobile == mobile && o.Purpose == purpose && !o.IsUsed && o.ExpiresAt > now)
.OrderByDescending(o => o.Created)
.FirstOrDefaultAsync(cancellationToken);
if (lastActive is not null && (now - lastActive.Created) < Cooldown)
return new CreateNewOtpTokenResponseDto()
{
Success = false,
Message = "لطفاً کمی بعد دوباره تلاش کنید."
};
// تولید کد
var code = GenerateNumericCode(CodeLength);
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
var codeHash = _hashService.ComputeHmacSha256Hex(code, secret);
var entity = new OtpToken
{
Mobile = mobile,
Purpose = purpose,
CodeHash = codeHash,
ExpiresAt = now.Add(Ttl),
Attempts = 0,
IsUsed = false,
};
await _context.OtpTokens.AddAsync(entity, cancellationToken);
entity.AddDomainEvent(new CreateNewOtpTokenEvent(entity, code, request.SignGuid));
await _context.SaveChangesAsync(cancellationToken);
return new CreateNewOtpTokenResponseDto()
{
Success = true,
Message = "کد ارسال شد.",
Code = code,
RemainingAttempts = MaxAttempts,
RemainingSeconds = Ttl.Seconds
};
}
// --- util‌ها ---
private string GenerateNumericCode(int len)
{
// امن‌تر از Random(): تولید ارقام با RNG
var bytes = new byte[len];
RandomNumberGenerator.Fill(bytes);
var sb = new StringBuilder(len);
foreach (var b in bytes) sb.Append((b % 10).ToString());
return sb.ToString();
}
}