Generator Changes at 9/27/2025 11:07:17 PM
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
<PackageReference Include="Mapster" Version="7.3.0" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace CMSMicroservice.Application.Common.Extensions;
|
||||
public static class UtilExtensions
|
||||
{
|
||||
public static string NormalizeIranMobile(this string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input)) throw new ArgumentException("mobile is empty");
|
||||
var m = new string(input.Where(char.IsDigit).ToArray());
|
||||
|
||||
if (m.StartsWith("0098")) m = m[4..];
|
||||
else if (m.StartsWith("098")) m = m[3..];
|
||||
else if (m.StartsWith("98")) m = m[2..];
|
||||
if (!m.StartsWith("0")) m = "0" + m;
|
||||
|
||||
if (m.Length != 11 || !m.StartsWith("09"))
|
||||
throw new ArgumentException("شماره موبایل نامعتبر است.");
|
||||
return m;
|
||||
}
|
||||
public static string Generate(int digits = 10, bool firstDigitNonZero = false)
|
||||
{
|
||||
if (digits <= 0) throw new ArgumentOutOfRangeException(nameof(digits));
|
||||
|
||||
var chars = new char[digits];
|
||||
|
||||
// رقم اول
|
||||
if (firstDigitNonZero)
|
||||
chars[0] = (char)('0' + RandomNumberGenerator.GetInt32(1, 10)); // 1..9
|
||||
else
|
||||
chars[0] = (char)('0' + RandomNumberGenerator.GetInt32(10)); // 0..9
|
||||
|
||||
// بقیه ارقام
|
||||
for (int i = 1; i < digits; i++)
|
||||
chars[i] = (char)('0' + RandomNumberGenerator.GetInt32(10));
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
|
||||
public interface IApplicationDbContext
|
||||
{
|
||||
DbSet<User> Users { get; }
|
||||
DbSet<UserAddress> UserAddresss { get; }
|
||||
DbSet<Package> Packages { get; }
|
||||
DbSet<UserOrder> UserOrders { get; }
|
||||
DbSet<Role> Roles { get; }
|
||||
DbSet<UserRole> UserRoles { get; }
|
||||
DbSet<User> Users { get; }
|
||||
DbSet<OtpToken> OtpTokens { get; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Application.Common.Mappings;
|
||||
|
||||
public class OtpTokenProfile : IRegister
|
||||
{
|
||||
void IRegister.Register(TypeAdapterConfig config)
|
||||
{
|
||||
//config.NewConfig<Source,Destination>()
|
||||
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.CreateNewOtpToken;
|
||||
public record CreateNewOtpTokenCommand : IRequest<CreateNewOtpTokenResponseDto>
|
||||
{
|
||||
//موبایل مقصد
|
||||
public string Mobile { get; init; }
|
||||
//مقصود
|
||||
public string Purpose { get; init; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
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;
|
||||
|
||||
public CreateNewOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
}
|
||||
|
||||
const int CodeLength = 6; // 4-6 مناسب است
|
||||
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 codeHash = Hash(code);
|
||||
|
||||
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));
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return new CreateNewOtpTokenResponseDto()
|
||||
{
|
||||
Success = true,
|
||||
Message = "کد ارسال شد.",
|
||||
Code = code
|
||||
};
|
||||
}
|
||||
|
||||
// --- 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();
|
||||
}
|
||||
|
||||
private string Hash(string code)
|
||||
{
|
||||
// HMAC با secret اپ (نیازی به ذخیره salt جدا نیست)
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
var hash = h.ComputeHash(Encoding.UTF8.GetBytes(code));
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.CreateNewOtpToken;
|
||||
public class CreateNewOtpTokenCommandValidator : AbstractValidator<CreateNewOtpTokenCommand>
|
||||
{
|
||||
public CreateNewOtpTokenCommandValidator()
|
||||
{
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Purpose)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<CreateNewOtpTokenCommand>.CreateWithOptions((CreateNewOtpTokenCommand)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.CreateNewOtpToken;
|
||||
public class CreateNewOtpTokenResponseDto
|
||||
{
|
||||
//موفق؟
|
||||
public bool Success { get; set; }
|
||||
//پیام
|
||||
public string Message { get; set; }
|
||||
//کد
|
||||
public string? Code { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
public record VerifyOtpTokenCommand : IRequest<VerifyOtpTokenResponseDto>
|
||||
{
|
||||
//موبایل مقصد
|
||||
public string Mobile { get; init; }
|
||||
//مقصود
|
||||
public string Purpose { get; init; }
|
||||
//کد
|
||||
public string Code { get; init; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenCommand, VerifyOtpTokenResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IConfiguration _cfg;
|
||||
|
||||
public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
}
|
||||
|
||||
const int MaxAttempts = 5; // محدودیت تلاش
|
||||
public async Task<VerifyOtpTokenResponseDto> Handle(VerifyOtpTokenCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var mobile = request.Mobile.NormalizeIranMobile();
|
||||
var purpose = request.Purpose?.ToLowerInvariant() ?? "signup";
|
||||
var now = DateTime.Now;
|
||||
|
||||
|
||||
var otp = await _context.OtpTokens
|
||||
.Where(o => o.Mobile == mobile && o.Purpose == purpose && !o.IsUsed && o.ExpiresAt > now)
|
||||
.OrderByDescending(o => o.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (otp is null) return new VerifyOtpTokenResponseDto() { Success = false, Message = "کد پیدا نشد یا منقضی شده است." };
|
||||
|
||||
if (otp.Attempts >= MaxAttempts) return new VerifyOtpTokenResponseDto() { Success = false, Message = "تعداد تلاشها زیاد است. دوباره کد بگیرید." };
|
||||
|
||||
otp.Attempts++;
|
||||
|
||||
if (!VerifyHash(request.Code, otp.CodeHash))
|
||||
{
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return new VerifyOtpTokenResponseDto() { Success = false, Message = "کد نادرست است." };
|
||||
}
|
||||
|
||||
// موفق
|
||||
otp.IsUsed = true;
|
||||
|
||||
// کاربر را بساز/بهروزرسانی کن
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Mobile == mobile, cancellationToken);
|
||||
if (user is null)
|
||||
{
|
||||
user = new User
|
||||
{
|
||||
Mobile = mobile,
|
||||
ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true),
|
||||
IsMobileVerified = true,
|
||||
MobileVerifiedAt = now
|
||||
};
|
||||
await _context.Users.AddAsync(user, cancellationToken);
|
||||
user.AddDomainEvent(new CreateNewUserEvent(user));
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.IsMobileVerified = true;
|
||||
user.MobileVerifiedAt ??= now;
|
||||
|
||||
_context.Users.Update(user);
|
||||
user.AddDomainEvent(new UpdateUserEvent(user));
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return new VerifyOtpTokenResponseDto()
|
||||
{
|
||||
Success = true,
|
||||
Message = "اعتبارسنجی موفق."
|
||||
};
|
||||
}
|
||||
|
||||
private string Hash(string code)
|
||||
{
|
||||
// HMAC با secret اپ (نیازی به ذخیره salt جدا نیست)
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
var hash = h.ComputeHash(Encoding.UTF8.GetBytes(code));
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
private bool VerifyHash(string code, string hexHash) => Hash(code).Equals(hexHash, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
public class VerifyOtpTokenCommandValidator : AbstractValidator<VerifyOtpTokenCommand>
|
||||
{
|
||||
public VerifyOtpTokenCommandValidator()
|
||||
{
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Purpose)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Code)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<VerifyOtpTokenCommand>.CreateWithOptions((VerifyOtpTokenCommand)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
public class VerifyOtpTokenResponseDto
|
||||
{
|
||||
//موفق؟
|
||||
public bool Success { get; set; }
|
||||
//پیام
|
||||
public string Message { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.EventHandlers;
|
||||
|
||||
public class CreateNewOtpTokenEventHandler : INotificationHandler<CreateNewOtpTokenEvent>
|
||||
{
|
||||
private readonly ILogger<CreateNewOtpTokenEventHandler> _logger;
|
||||
|
||||
public CreateNewOtpTokenEventHandler(ILogger<CreateNewOtpTokenEventHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task Handle(CreateNewOtpTokenEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.EventHandlers;
|
||||
|
||||
public class VerifyOtpTokenEventHandler : INotificationHandler<VerifyOtpTokenEvent>
|
||||
{
|
||||
private readonly ILogger<VerifyOtpTokenEventHandler> _logger;
|
||||
|
||||
public VerifyOtpTokenEventHandler(ILogger<VerifyOtpTokenEventHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task Handle(VerifyOtpTokenEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Queries.GetAllOtpTokenByFilter;
|
||||
public record GetAllOtpTokenByFilterQuery : IRequest<GetAllOtpTokenByFilterResponseDto>
|
||||
{
|
||||
//موقعیت صفحه بندی
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
//مرتب سازی بر اساس
|
||||
public string? SortBy { get; init; }
|
||||
//فیلتر
|
||||
public GetAllOtpTokenByFilterFilter? Filter { get; init; }
|
||||
|
||||
}public class GetAllOtpTokenByFilterFilter
|
||||
{
|
||||
//شناسه
|
||||
public long? Id { get; set; }
|
||||
//موبایل مقصد
|
||||
public string? Mobile { get; set; }
|
||||
//مقصود
|
||||
public string? Purpose { get; set; }
|
||||
//کد هش شده
|
||||
public string? CodeHash { get; set; }
|
||||
//زمان انقضا
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
//شمارش تلاشها
|
||||
public int? Attempts { get; set; }
|
||||
//موفق بود؟
|
||||
public bool? IsUsed { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Queries.GetAllOtpTokenByFilter;
|
||||
public class GetAllOtpTokenByFilterQueryHandler : IRequestHandler<GetAllOtpTokenByFilterQuery, GetAllOtpTokenByFilterResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllOtpTokenByFilterQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllOtpTokenByFilterResponseDto> Handle(GetAllOtpTokenByFilterQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.OtpTokens
|
||||
.ApplyOrder(sortBy: request.SortBy)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
if (request.Filter is not null)
|
||||
{
|
||||
query = query
|
||||
.Where(x => request.Filter.Id == null || x.Id == request.Filter.Id)
|
||||
.Where(x => request.Filter.Mobile == null || x.Mobile.Contains(request.Filter.Mobile))
|
||||
.Where(x => request.Filter.Purpose == null || x.Purpose.Contains(request.Filter.Purpose))
|
||||
.Where(x => request.Filter.CodeHash == null || x.CodeHash.Contains(request.Filter.CodeHash))
|
||||
.Where(x => request.Filter.ExpiresAt == null || x.ExpiresAt == request.Filter.ExpiresAt)
|
||||
.Where(x => request.Filter.Attempts == null || x.Attempts == request.Filter.Attempts)
|
||||
.Where(x => request.Filter.IsUsed == null || x.IsUsed == request.Filter.IsUsed)
|
||||
;
|
||||
}
|
||||
return new GetAllOtpTokenByFilterResponseDto
|
||||
{
|
||||
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
|
||||
Models = await query.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.ProjectToType<GetAllOtpTokenByFilterResponseModel>().ToListAsync(cancellationToken)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Queries.GetAllOtpTokenByFilter;
|
||||
public class GetAllOtpTokenByFilterQueryValidator : AbstractValidator<GetAllOtpTokenByFilterQuery>
|
||||
{
|
||||
public GetAllOtpTokenByFilterQueryValidator()
|
||||
{
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<GetAllOtpTokenByFilterQuery>.CreateWithOptions((GetAllOtpTokenByFilterQuery)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Queries.GetAllOtpTokenByFilter;
|
||||
public class GetAllOtpTokenByFilterResponseDto
|
||||
{
|
||||
//متادیتا
|
||||
public MetaData MetaData { get; set; }
|
||||
//مدل خروجی
|
||||
public List<GetAllOtpTokenByFilterResponseModel>? Models { get; set; }
|
||||
|
||||
}public class GetAllOtpTokenByFilterResponseModel
|
||||
{
|
||||
//شناسه
|
||||
public long Id { get; set; }
|
||||
//موبایل مقصد
|
||||
public string Mobile { get; set; }
|
||||
//مقصود
|
||||
public string Purpose { get; set; }
|
||||
//کد هش شده
|
||||
public string CodeHash { get; set; }
|
||||
//زمان انقضا
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
//شمارش تلاشها
|
||||
public int Attempts { get; set; }
|
||||
//موفق بود؟
|
||||
public bool IsUsed { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using System.Security.Cryptography;
|
||||
namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewUser;
|
||||
public class CreateNewUserCommandHandler : IRequestHandler<CreateNewUserCommand, CreateNewUserResponseDto>
|
||||
{
|
||||
@@ -13,6 +14,8 @@ public class CreateNewUserCommandHandler : IRequestHandler<CreateNewUserCommand,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = request.Adapt<User>();
|
||||
entity.ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true);
|
||||
|
||||
await _context.Users.AddAsync(entity, cancellationToken);
|
||||
entity.AddDomainEvent(new CreateNewUserEvent(entity));
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -7,13 +7,9 @@ public record UpdateUserCommand : IRequest<Unit>
|
||||
public string? FirstName { get; init; }
|
||||
//نام خانوادگی
|
||||
public string? LastName { get; init; }
|
||||
//شماره موبایل
|
||||
public string Mobile { get; init; }
|
||||
//کد ملی
|
||||
public string? NationalCode { get; init; }
|
||||
//آدرس آواتار
|
||||
public string? AvatarPath { get; init; }
|
||||
//شناسه والد
|
||||
public long? ParentId { get; init; }
|
||||
|
||||
}
|
||||
@@ -5,8 +5,6 @@ public class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
|
||||
{
|
||||
RuleFor(model => model.Id)
|
||||
.NotNull();
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
|
||||
@@ -24,4 +24,10 @@ public record GetAllUserByFilterQuery : IRequest<GetAllUserByFilterResponseDto>
|
||||
public string? AvatarPath { get; set; }
|
||||
//شناسه والد
|
||||
public long? ParentId { get; set; }
|
||||
//کد ارجاع
|
||||
public string? ReferralCode { get; set; }
|
||||
//موبایل فعال شده؟
|
||||
public bool? IsMobileVerified { get; set; }
|
||||
//تاریخ فعال سازی موبایل
|
||||
public DateTime? MobileVerifiedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public class GetAllUserByFilterQueryHandler : IRequestHandler<GetAllUserByFilter
|
||||
.Where(x => request.Filter.Mobile == null || x.Mobile.Contains(request.Filter.Mobile))
|
||||
.Where(x => request.Filter.NationalCode == null || x.NationalCode.Contains(request.Filter.NationalCode))
|
||||
.Where(x => request.Filter.AvatarPath == null || x.AvatarPath.Contains(request.Filter.AvatarPath))
|
||||
.Where(x => request.Filter.ReferralCode == null || x.ReferralCode == request.Filter.ReferralCode)
|
||||
.Where(x => request.Filter.IsMobileVerified == null || x.IsMobileVerified == request.Filter.IsMobileVerified)
|
||||
.Where(x => request.Filter.ParentId == null || x.ParentId == request.Filter.ParentId)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -22,4 +22,10 @@ public class GetAllUserByFilterResponseDto
|
||||
public string? AvatarPath { get; set; }
|
||||
//شناسه والد
|
||||
public long? ParentId { get; set; }
|
||||
//کد ارجاع
|
||||
public string ReferralCode { get; set; }
|
||||
//موبایل فعال شده؟
|
||||
public bool IsMobileVerified { get; set; }
|
||||
//تاریخ فعال سازی موبایل
|
||||
public DateTime? MobileVerifiedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -15,5 +15,11 @@ public class GetUserResponseDto
|
||||
public string? AvatarPath { get; set; }
|
||||
//شناسه والد
|
||||
public long? ParentId { get; set; }
|
||||
//کد ارجاع
|
||||
public string ReferralCode { get; set; }
|
||||
//موبایل فعال شده؟
|
||||
public bool IsMobileVerified { get; set; }
|
||||
//تاریخ فعال سازی موبایل
|
||||
public DateTime? MobileVerifiedAt { get; set; }
|
||||
|
||||
}
|
||||
17
src/CMSMicroservice.Domain/Entities/OtpToken.cs
Normal file
17
src/CMSMicroservice.Domain/Entities/OtpToken.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace CMSMicroservice.Domain.Entities;
|
||||
//توکن Otp
|
||||
public class OtpToken : BaseAuditableEntity
|
||||
{
|
||||
//موبایل مقصد
|
||||
public string Mobile { get; set; }
|
||||
//مقصود
|
||||
public string Purpose { get; set; }
|
||||
//کد هش شده
|
||||
public string CodeHash { get; set; }
|
||||
//زمان انقضا
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
//شمارش تلاشها
|
||||
public int Attempts { get; set; }
|
||||
//موفق بود؟
|
||||
public bool IsUsed { get; set; }
|
||||
}
|
||||
@@ -16,12 +16,18 @@ public class User : BaseAuditableEntity
|
||||
public long? ParentId { get; set; }
|
||||
//User Navigation Property
|
||||
public virtual User? Parent { get; set; }
|
||||
//User Collection Navigation Reference
|
||||
public virtual ICollection<User> Users { get; set; }
|
||||
//کد ارجاع
|
||||
public string ReferralCode { get; set; }
|
||||
//موبایل فعال شده؟
|
||||
public bool IsMobileVerified { get; set; }
|
||||
//تاریخ فعال سازی موبایل
|
||||
public DateTime? MobileVerifiedAt { get; set; }
|
||||
//UserAddress Collection Navigation Reference
|
||||
public virtual ICollection<UserAddress> UserAddresss { get; set; }
|
||||
//UserOrder Collection Navigation Reference
|
||||
public virtual ICollection<UserOrder> UserOrders { get; set; }
|
||||
//UserRole Collection Navigation Reference
|
||||
public virtual ICollection<UserRole> UserRoles { get; set; }
|
||||
//User Collection Navigation Reference
|
||||
public virtual ICollection<User> Users { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Domain.Events;
|
||||
public class CreateNewOtpTokenEvent : BaseEvent
|
||||
{
|
||||
public CreateNewOtpTokenEvent(OtpToken item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public OtpToken Item { get; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Domain.Events;
|
||||
public class VerifyOtpTokenEvent : BaseEvent
|
||||
{
|
||||
public VerifyOtpTokenEvent(OtpToken item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public OtpToken Item { get; }
|
||||
}
|
||||
@@ -39,11 +39,12 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
|
||||
|
||||
return await base.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<UserAddress> UserAddresss => Set<UserAddress>();
|
||||
public DbSet<Package> Packages => Set<Package>();
|
||||
public DbSet<UserOrder> UserOrders => Set<UserOrder>();
|
||||
public DbSet<Role> Roles => Set<Role>();
|
||||
public DbSet<UserRole> UserRoles => Set<UserRole>();
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<OtpToken> OtpTokens => Set<OtpToken>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
|
||||
|
||||
public class OtpTokenConfiguration : IEntityTypeConfiguration<OtpToken>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<OtpToken> builder)
|
||||
{
|
||||
builder.HasQueryFilter(p => !p.IsDeleted);
|
||||
builder.Ignore(entity => entity.DomainEvents);
|
||||
builder.HasKey(entity => entity.Id);
|
||||
builder.Property(entity => entity.Id).UseIdentityColumn();
|
||||
builder.Property(entity => entity.Mobile).IsRequired(true);
|
||||
builder.Property(entity => entity.Purpose).IsRequired(true);
|
||||
builder.Property(entity => entity.CodeHash).IsRequired(true);
|
||||
builder.Property(entity => entity.ExpiresAt).IsRequired(true);
|
||||
builder.Property(entity => entity.Attempts).IsRequired(true);
|
||||
builder.Property(entity => entity.IsUsed).IsRequired(true);
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,8 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
.WithMany(entity => entity.Users)
|
||||
.HasForeignKey(entity => entity.ParentId)
|
||||
.IsRequired(false);
|
||||
builder.Property(entity => entity.ReferralCode).IsRequired(true);
|
||||
builder.Property(entity => entity.IsMobileVerified ).IsRequired(true);
|
||||
builder.Property(entity => entity.MobileVerifiedAt).IsRequired(false);
|
||||
}
|
||||
}
|
||||
436
src/CMSMicroservice.Infrastructure/Persistence/Migrations/20250927194115_u02.Designer.cs
generated
Normal file
436
src/CMSMicroservice.Infrastructure/Persistence/Migrations/20250927194115_u02.Designer.cs
generated
Normal file
@@ -0,0 +1,436 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CMSMicroservice.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250927194115_u02")]
|
||||
partial class u02
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("CMS")
|
||||
.HasAnnotation("ProductVersion", "7.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("Attempts")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CodeHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Purpose")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("OtpTokens", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("Price")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Packages", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Roles", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AvatarPath")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMobileVerified")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("MobileVerifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("NationalCode")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ParentId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ReferralCode")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("Users", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("CityId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDefault")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PostalCode")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserAddresss", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("PaymentDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("PaymentStatus")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<long>("Price")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long?>("TransactionId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserOrders", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("RoleId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserRoles", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "Parent")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("ParentId");
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany("UserAddresss")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany("UserOrders")
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany("UserOrders")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role")
|
||||
.WithMany("UserRoles")
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany("UserRoles")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
||||
{
|
||||
b.Navigation("UserOrders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b =>
|
||||
{
|
||||
b.Navigation("UserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("UserAddresss");
|
||||
|
||||
b.Navigation("UserOrders");
|
||||
|
||||
b.Navigation("UserRoles");
|
||||
|
||||
b.Navigation("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class u02 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsMobileVerified",
|
||||
schema: "CMS",
|
||||
table: "Users",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "MobileVerifiedAt",
|
||||
schema: "CMS",
|
||||
table: "Users",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReferralCode",
|
||||
schema: "CMS",
|
||||
table: "Users",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OtpTokens",
|
||||
schema: "CMS",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Mobile = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Purpose = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
CodeHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
Attempts = table.Column<int>(type: "int", nullable: false),
|
||||
IsUsed = table.Column<bool>(type: "bit", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OtpTokens", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OtpTokens",
|
||||
schema: "CMS");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsMobileVerified",
|
||||
schema: "CMS",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MobileVerifiedAt",
|
||||
schema: "CMS",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReferralCode",
|
||||
schema: "CMS",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,55 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("Attempts")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CodeHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Purpose")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("OtpTokens", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -125,6 +174,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMobileVerified")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
@@ -138,12 +190,19 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("MobileVerifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("NationalCode")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ParentId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ReferralCode")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.111</Version>
|
||||
<Version>0.0.112</Version>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
@@ -29,6 +29,7 @@
|
||||
<Protobuf Include="Protos\userorder.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<Protobuf Include="Protos\role.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<Protobuf Include="Protos\userrole.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<Protobuf Include="Protos\otptoken.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<Protobuf Include="Protos\public_messages.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
87
src/CMSMicroservice.Protobuf/Protos/otptoken.proto
Normal file
87
src/CMSMicroservice.Protobuf/Protos/otptoken.proto
Normal file
@@ -0,0 +1,87 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package otptoken;
|
||||
|
||||
import "public_messages.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
option csharp_namespace = "CMSMicroservice.Protobuf.Protos.OtpToken";
|
||||
|
||||
service OtpTokenContract
|
||||
{
|
||||
rpc CreateNewOtpToken(CreateNewOtpTokenRequest) returns (CreateNewOtpTokenResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/CreateNewOtpToken"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc VerifyOtpToken(VerifyOtpTokenRequest) returns (VerifyOtpTokenResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/VerifyOtpToken"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc GetAllOtpTokenByFilter(GetAllOtpTokenByFilterRequest) returns (GetAllOtpTokenByFilterResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetAllOtpTokenByFilter"
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
message CreateNewOtpTokenRequest
|
||||
{
|
||||
string mobile = 1;
|
||||
string purpose = 2;
|
||||
}
|
||||
message CreateNewOtpTokenResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
google.protobuf.StringValue code = 3;
|
||||
}
|
||||
message VerifyOtpTokenRequest
|
||||
{
|
||||
string mobile = 1;
|
||||
string purpose = 2;
|
||||
string code = 3;
|
||||
}
|
||||
message VerifyOtpTokenResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
message GetAllOtpTokenByFilterRequest
|
||||
{
|
||||
messages.PaginationState pagination_state = 1;
|
||||
google.protobuf.StringValue sort_by = 2;
|
||||
GetAllOtpTokenByFilterFilter filter = 3;
|
||||
}
|
||||
message GetAllOtpTokenByFilterFilter
|
||||
{
|
||||
google.protobuf.Int64Value id = 1;
|
||||
google.protobuf.StringValue mobile = 2;
|
||||
google.protobuf.StringValue purpose = 3;
|
||||
google.protobuf.StringValue code_hash = 4;
|
||||
google.protobuf.Timestamp expires_at = 5;
|
||||
google.protobuf.Int32Value attempts = 6;
|
||||
google.protobuf.BoolValue is_used = 7;
|
||||
}
|
||||
message GetAllOtpTokenByFilterResponse
|
||||
{
|
||||
messages.MetaData meta_data = 1;
|
||||
repeated GetAllOtpTokenByFilterResponseModel models = 2;
|
||||
}
|
||||
message GetAllOtpTokenByFilterResponseModel
|
||||
{
|
||||
int64 id = 1;
|
||||
string mobile = 2;
|
||||
string purpose = 3;
|
||||
string code_hash = 4;
|
||||
google.protobuf.Timestamp expires_at = 5;
|
||||
int32 attempts = 6;
|
||||
bool is_used = 7;
|
||||
}
|
||||
@@ -62,10 +62,8 @@ message UpdateUserRequest
|
||||
int64 id = 1;
|
||||
google.protobuf.StringValue first_name = 2;
|
||||
google.protobuf.StringValue last_name = 3;
|
||||
string mobile = 4;
|
||||
google.protobuf.StringValue national_code = 5;
|
||||
google.protobuf.StringValue avatar_path = 6;
|
||||
google.protobuf.Int64Value parent_id = 7;
|
||||
google.protobuf.StringValue national_code = 4;
|
||||
google.protobuf.StringValue avatar_path = 5;
|
||||
}
|
||||
message DeleteUserRequest
|
||||
{
|
||||
@@ -84,6 +82,9 @@ message GetUserResponse
|
||||
google.protobuf.StringValue national_code = 5;
|
||||
google.protobuf.StringValue avatar_path = 6;
|
||||
google.protobuf.Int64Value parent_id = 7;
|
||||
string referral_code = 8;
|
||||
bool is_mobile_verified = 9;
|
||||
google.protobuf.Timestamp mobile_verified_at = 10;
|
||||
}
|
||||
message GetAllUserByFilterRequest
|
||||
{
|
||||
@@ -100,6 +101,9 @@ message GetAllUserByFilterFilter
|
||||
google.protobuf.StringValue national_code = 5;
|
||||
google.protobuf.StringValue avatar_path = 6;
|
||||
google.protobuf.Int64Value parent_id = 7;
|
||||
google.protobuf.StringValue referral_code = 8;
|
||||
google.protobuf.BoolValue is_mobile_verified = 9;
|
||||
google.protobuf.Timestamp mobile_verified_at = 10;
|
||||
}
|
||||
message GetAllUserByFilterResponse
|
||||
{
|
||||
@@ -115,4 +119,7 @@ message GetAllUserByFilterResponseModel
|
||||
google.protobuf.StringValue national_code = 5;
|
||||
google.protobuf.StringValue avatar_path = 6;
|
||||
google.protobuf.Int64Value parent_id = 7;
|
||||
string referral_code = 8;
|
||||
bool is_mobile_verified = 9;
|
||||
google.protobuf.Timestamp mobile_verified_at = 10;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using CMSMicroservice.Protobuf.Protos.OtpToken;
|
||||
namespace CMSMicroservice.Protobuf.Validator.OtpToken;
|
||||
|
||||
public class CreateNewOtpTokenRequestValidator : AbstractValidator<CreateNewOtpTokenRequest>
|
||||
{
|
||||
public CreateNewOtpTokenRequestValidator()
|
||||
{
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Purpose)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<CreateNewOtpTokenRequest>.CreateWithOptions((CreateNewOtpTokenRequest)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using CMSMicroservice.Protobuf.Protos.OtpToken;
|
||||
namespace CMSMicroservice.Protobuf.Validator.OtpToken;
|
||||
|
||||
public class GetAllOtpTokenByFilterRequestValidator : AbstractValidator<GetAllOtpTokenByFilterRequest>
|
||||
{
|
||||
public GetAllOtpTokenByFilterRequestValidator()
|
||||
{
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<GetAllOtpTokenByFilterRequest>.CreateWithOptions((GetAllOtpTokenByFilterRequest)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
using CMSMicroservice.Protobuf.Protos.OtpToken;
|
||||
namespace CMSMicroservice.Protobuf.Validator.OtpToken;
|
||||
|
||||
public class VerifyOtpTokenRequestValidator : AbstractValidator<VerifyOtpTokenRequest>
|
||||
{
|
||||
public VerifyOtpTokenRequestValidator()
|
||||
{
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Purpose)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Code)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<VerifyOtpTokenRequest>.CreateWithOptions((VerifyOtpTokenRequest)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
@@ -8,8 +8,6 @@ public class UpdateUserRequestValidator : AbstractValidator<UpdateUserRequest>
|
||||
{
|
||||
RuleFor(model => model.Id)
|
||||
.NotNull();
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||
|
||||
public class OtpTokenProfile : IRegister
|
||||
{
|
||||
void IRegister.Register(TypeAdapterConfig config)
|
||||
{
|
||||
//config.NewConfig<Source,Destination>()
|
||||
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
|
||||
}
|
||||
}
|
||||
27
src/CMSMicroservice.WebApi/Services/OtpTokenService.cs
Normal file
27
src/CMSMicroservice.WebApi/Services/OtpTokenService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using CMSMicroservice.Protobuf.Protos.OtpToken;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.OtpTokenCQ.Commands.CreateNewOtpToken;
|
||||
using CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
using CMSMicroservice.Application.OtpTokenCQ.Queries.GetAllOtpTokenByFilter;
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class OtpTokenService : OtpTokenContract.OtpTokenContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
|
||||
public OtpTokenService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
}
|
||||
public override async Task<CreateNewOtpTokenResponse> CreateNewOtpToken(CreateNewOtpTokenRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<CreateNewOtpTokenRequest, CreateNewOtpTokenCommand, CreateNewOtpTokenResponse>(request, context);
|
||||
}
|
||||
public override async Task<VerifyOtpTokenResponse> VerifyOtpToken(VerifyOtpTokenRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<VerifyOtpTokenRequest, VerifyOtpTokenCommand, VerifyOtpTokenResponse>(request, context);
|
||||
}
|
||||
public override async Task<GetAllOtpTokenByFilterResponse> GetAllOtpTokenByFilter(GetAllOtpTokenByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllOtpTokenByFilterRequest, GetAllOtpTokenByFilterQuery, GetAllOtpTokenByFilterResponse>(request, context);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"DefaultConnection": "Data Source=185.252.31.42,2019; Initial Catalog=Foursat;User ID=afrino;Password=87zH26nbqT%;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False",
|
||||
"providerName": "System.Data.SqlClient"
|
||||
},
|
||||
"Otp": {
|
||||
"Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M="
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"EndpointDefaults": {
|
||||
|
||||
Reference in New Issue
Block a user