diff --git a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
index c335a84..af74491 100644
--- a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
+++ b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQuery.cs b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQuery.cs
new file mode 100644
index 0000000..ebfe8e4
--- /dev/null
+++ b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQuery.cs
@@ -0,0 +1,42 @@
+namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
+
+public record GetAllCitiesByFilterQuery : IRequest
+{
+ ///
+ /// موقعیت صفحه بندی
+ ///
+ public PaginationState? PaginationState { get; init; }
+
+ ///
+ /// مرتب سازی بر اساس
+ ///
+ public string? SortBy { get; init; }
+
+ ///
+ /// فیلتر
+ ///
+ public GetAllCitiesByFilterFilter? Filter { get; init; }
+}
+
+public class GetAllCitiesByFilterFilter
+{
+ ///
+ /// شناسه
+ ///
+ public long? Id { get; set; }
+
+ ///
+ /// نام شهر (Contains)
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// نام بومی شهر (Contains)
+ ///
+ public string? Native { get; set; }
+
+ ///
+ /// شناسه استان
+ ///
+ public long? StateId { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryHandler.cs b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryHandler.cs
new file mode 100644
index 0000000..cd677f1
--- /dev/null
+++ b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryHandler.cs
@@ -0,0 +1,43 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
+
+public class GetAllCitiesByFilterQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+
+ public GetAllCitiesByFilterQueryHandler(IApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ GetAllCitiesByFilterQuery request,
+ CancellationToken cancellationToken)
+ {
+ var query = _context.Cities
+ .Include(c => c.State)
+ .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.Name == null || x.Name.Contains(request.Filter.Name))
+ .Where(x => request.Filter.Native == null || x.Native.Contains(request.Filter.Native))
+ .Where(x => request.Filter.StateId == null || x.StateId == request.Filter.StateId);
+ }
+
+ return new GetAllCitiesByFilterResponseDto
+ {
+ MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
+ Models = await query
+ .PaginatedListAsync(paginationState: request.PaginationState)
+ .ProjectToType()
+ .ToListAsync(cancellationToken)
+ };
+ }
+}
diff --git a/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryValidator.cs b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryValidator.cs
new file mode 100644
index 0000000..b2ed06a
--- /dev/null
+++ b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterQueryValidator.cs
@@ -0,0 +1,20 @@
+using FluentValidation;
+
+namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
+
+public class GetAllCitiesByFilterQueryValidator : AbstractValidator
+{
+ public GetAllCitiesByFilterQueryValidator()
+ {
+ // Validation rules اگر نیاز باشد
+ }
+
+ public async Task Validate(object model, string propertyName)
+ {
+ var result = await ValidateAsync(
+ ValidationContext.CreateWithOptions(
+ (GetAllCitiesByFilterQuery)model,
+ x => x.IncludeProperties(propertyName)));
+ return result.IsValid;
+ }
+}
diff --git a/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterResponseDto.cs b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterResponseDto.cs
new file mode 100644
index 0000000..ba2b553
--- /dev/null
+++ b/src/CMSMicroservice.Application/CityCQ/Queries/GetAllCitiesByFilter/GetAllCitiesByFilterResponseDto.cs
@@ -0,0 +1,62 @@
+namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
+
+public class GetAllCitiesByFilterResponseDto
+{
+ ///
+ /// متادیتا
+ ///
+ public MetaData MetaData { get; set; } = null!;
+
+ ///
+ /// مدل خروجی
+ ///
+ public List? Models { get; set; }
+}
+
+public class GetAllCitiesByFilterResponseModel
+{
+ ///
+ /// شناسه
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// شناسه خارجی
+ ///
+ public long ExternalId { get; set; }
+
+ ///
+ /// نام شهر
+ ///
+ public string Name { get; set; } = null!;
+
+ ///
+ /// نام بومی شهر (فارسی)
+ ///
+ public string Native { get; set; } = null!;
+
+ ///
+ /// عرض جغرافیایی
+ ///
+ public string Latitude { get; set; } = null!;
+
+ ///
+ /// طول جغرافیایی
+ ///
+ public string Longitude { get; set; } = null!;
+
+ ///
+ /// شناسه استان
+ ///
+ public long StateId { get; set; }
+
+ ///
+ /// نام استان
+ ///
+ public string StateName { get; set; } = null!;
+
+ ///
+ /// نام بومی استان
+ ///
+ public string StateNative { get; set; } = null!;
+}
diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommand.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommand.cs
new file mode 100644
index 0000000..2187728
--- /dev/null
+++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommand.cs
@@ -0,0 +1,48 @@
+namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
+
+///
+/// Command برای پذیرش قرارداد باشگاه مشتریان
+///
+public record AcceptClubMembershipContractCommand : IRequest
+{
+ ///
+ /// شناسه کاربر
+ ///
+ public long UserId { get; init; }
+
+ ///
+ /// کد OTP برای تایید
+ ///
+ public string OtpCode { get; init; }
+
+ ///
+ /// شناسه یکتای امضا (GUID)
+ ///
+ public string SignGuid { get; init; }
+
+ ///
+ /// محتوای HTML قرارداد
+ ///
+ public string ContractHtml { get; init; }
+}
+
+///
+/// DTO پاسخ پذیرش قرارداد باشگاه مشتریان
+///
+public class AcceptClubMembershipContractResponseDto
+{
+ ///
+ /// آیا عملیات موفق بود؟
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// پیام نتیجه
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// شناسه قرارداد ثبت شده
+ ///
+ public long ContractId { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs
new file mode 100644
index 0000000..16f0f1e
--- /dev/null
+++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs
@@ -0,0 +1,196 @@
+using CMSMicroservice.Application.Common.Exceptions;
+using CMSMicroservice.Domain.Entities;
+using CMSMicroservice.Domain.Enums;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
+
+///
+/// Handler برای پذیرش قرارداد باشگاه مشتریان
+/// این handler:
+/// 1. کد OTP را تایید میکند
+/// 2. قرارداد را در جدول UserContract ثبت میکند
+/// 3. باشگاه مشتری را فعال میکند (IsActive = true)
+///
+public class AcceptClubMembershipContractCommandHandler
+ : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly IConfiguration _cfg;
+ private readonly IHashService _hashService;
+ private readonly ILogger _logger;
+
+ private const int MaxAttempts = 5;
+ private const string OtpPurpose = "signClubContract";
+
+ public AcceptClubMembershipContractCommandHandler(
+ IApplicationDbContext context,
+ IConfiguration cfg,
+ IHashService hashService,
+ ILogger logger)
+ {
+ _context = context;
+ _cfg = cfg;
+ _hashService = hashService;
+ _logger = logger;
+ }
+
+ public async Task Handle(
+ AcceptClubMembershipContractCommand request,
+ CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Processing club membership contract for UserId: {UserId}",
+ request.UserId
+ );
+
+ // 1. دریافت کاربر
+ var user = await _context.Users
+ .Include(u => u.ClubMembership)
+ .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
+
+ if (user == null)
+ {
+ _logger.LogWarning("User not found: {UserId}", request.UserId);
+ throw new NotFoundException(nameof(User), request.UserId);
+ }
+
+ // 2. بررسی خرید پکیج
+ if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
+ {
+ return new AcceptClubMembershipContractResponseDto
+ {
+ Success = false,
+ Message = "برای امضای قرارداد باشگاه مشتریان ابتدا باید پکیج پایه را خریداری کنید"
+ };
+ }
+
+ // 3. بررسی عدم فعال بودن قبلی باشگاه
+ if (user.ClubMembership?.IsActive == true)
+ {
+ return new AcceptClubMembershipContractResponseDto
+ {
+ Success = false,
+ Message = "شما قبلاً عضو باشگاه مشتریان شدهاید"
+ };
+ }
+
+ // 4. تایید OTP
+ var otpResult = await VerifyOtpAsync(user.Mobile, request.OtpCode, cancellationToken);
+ if (!otpResult.Success)
+ {
+ return new AcceptClubMembershipContractResponseDto
+ {
+ Success = false,
+ Message = otpResult.Message
+ };
+ }
+
+ // 5. ثبت قرارداد در جدول UserContract
+ var contract = await _context.Contracts
+ .FirstOrDefaultAsync(c => c.Type == ContractType.ClubMembership, cancellationToken);
+
+ if (contract == null)
+ {
+ // اگر قرارداد وجود ندارد، یک قرارداد پیشفرض ایجاد کنید
+ contract = new Contract
+ {
+ Title = "قرارداد باشگاه مشتریان",
+ Description = "قوانین و مقررات باشگاه مشتریان کارابازار",
+ HtmlContent = request.ContractHtml,
+ Type = ContractType.ClubMembership
+ };
+ await _context.Contracts.AddAsync(contract, cancellationToken);
+ await _context.SaveChangesAsync(cancellationToken);
+ }
+
+ var userContract = new UserContract
+ {
+ UserId = user.Id,
+ ContractId = contract.Id,
+ SignGuid = request.SignGuid,
+ SignedPdfFile = request.ContractHtml
+ };
+ await _context.UserContracts.AddAsync(userContract, cancellationToken);
+
+ // 6. فعالسازی باشگاه مشتریان
+ if (user.ClubMembership == null)
+ {
+ user.ClubMembership = new Domain.Entities.Club.ClubMembership
+ {
+ UserId = user.Id,
+ IsActive = true,
+ ActivatedAt = DateTime.Now,
+ InitialContribution = 56_000_000,
+ GiftValue = 25_200_000,
+ PurchaseMethod = user.PackagePurchaseMethod
+ };
+ await _context.ClubMemberships.AddAsync(user.ClubMembership, cancellationToken);
+ }
+ else
+ {
+ user.ClubMembership.IsActive = true;
+ user.ClubMembership.ActivatedAt = DateTime.Now;
+ _context.ClubMemberships.Update(user.ClubMembership);
+ }
+
+ await _context.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Club membership contract accepted and activated for UserId: {UserId}, ContractId: {ContractId}",
+ request.UserId,
+ userContract.Id
+ );
+
+ return new AcceptClubMembershipContractResponseDto
+ {
+ Success = true,
+ Message = "قرارداد باشگاه مشتریان با موفقیت ثبت شد و عضویت شما فعال گردید",
+ ContractId = userContract.Id
+ };
+ }
+
+ private async Task<(bool Success, string Message)> VerifyOtpAsync(
+ string mobile,
+ string code,
+ CancellationToken cancellationToken)
+ {
+ var normalizedMobile = mobile.NormalizeIranMobile();
+ var now = DateTime.Now;
+
+ var otp = await _context.OtpTokens
+ .Where(o => o.Mobile == normalizedMobile
+ && o.Purpose == OtpPurpose
+ && !o.IsUsed
+ && o.ExpiresAt > now)
+ .OrderByDescending(o => o.Created)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ if (otp == null)
+ {
+ return (false, "کد تایید پیدا نشد یا منقضی شده است");
+ }
+
+ if (otp.Attempts >= MaxAttempts)
+ {
+ return (false, "تعداد تلاشها بیش از حد مجاز است. لطفاً کد جدید دریافت کنید");
+ }
+
+ otp.Attempts++;
+
+ var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not configured");
+
+ if (!_hashService.VerifyHmacSha256Hex(code, otp.CodeHash, secret))
+ {
+ await _context.SaveChangesAsync(cancellationToken);
+ return (false, "کد تایید نادرست است");
+ }
+
+ // کد صحیح است - علامتگذاری به عنوان استفاده شده
+ otp.IsUsed = true;
+ await _context.SaveChangesAsync(cancellationToken);
+
+ return (true, "کد تایید صحیح است");
+ }
+}
diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs
new file mode 100644
index 0000000..8c5f308
--- /dev/null
+++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs
@@ -0,0 +1,25 @@
+namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
+
+public class AcceptClubMembershipContractCommandValidator : AbstractValidator
+{
+ public AcceptClubMembershipContractCommandValidator()
+ {
+ RuleFor(x => x.UserId)
+ .GreaterThan(0)
+ .WithMessage("شناسه کاربر الزامی است");
+
+ RuleFor(x => x.OtpCode)
+ .NotEmpty()
+ .WithMessage("کد تایید الزامی است")
+ .Length(6)
+ .WithMessage("کد تایید باید ۶ رقم باشد");
+
+ RuleFor(x => x.SignGuid)
+ .NotEmpty()
+ .WithMessage("شناسه امضا الزامی است");
+
+ RuleFor(x => x.ContractHtml)
+ .NotEmpty()
+ .WithMessage("محتوای قرارداد الزامی است");
+ }
+}
diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
index 0677b7d..7b85fa8 100644
--- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
+++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
@@ -1,6 +1,7 @@
using CMSMicroservice.Domain.Entities.Payment;
using CMSMicroservice.Domain.Entities.Order;
using CMSMicroservice.Domain.Entities.DiscountShop;
+using CMSMicroservice.Domain.Entities.Geography;
namespace CMSMicroservice.Application.Common.Interfaces;
@@ -53,5 +54,10 @@ public interface IApplicationDbContext
DbSet DiscountOrders { get; }
DbSet DiscountOrderDetails { get; }
+ // ============= Geography =============
+ DbSet Countries { get; }
+ DbSet States { get; }
+ DbSet Cities { get; }
+
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/CMSMicroservice.Application/Common/Interfaces/ITokenNotificationService.cs b/src/CMSMicroservice.Application/Common/Interfaces/ITokenNotificationService.cs
new file mode 100644
index 0000000..08770e0
--- /dev/null
+++ b/src/CMSMicroservice.Application/Common/Interfaces/ITokenNotificationService.cs
@@ -0,0 +1,26 @@
+namespace CMSMicroservice.Application.Common.Interfaces;
+
+///
+/// Interface for sending token-related notifications via SignalR
+///
+public interface ITokenNotificationService
+{
+ ///
+ /// Notify that a user's token has been revoked and they should refresh their token
+ ///
+ /// The user ID whose token was revoked
+ /// The reason for revocation
+ Task NotifyTokenRevokedAsync(long userId, string reason);
+
+ ///
+ /// Notify that a user should refresh their token due to profile changes
+ ///
+ /// The user ID whose profile changed
+ Task NotifyForceRefreshAsync(long userId);
+
+ ///
+ /// Broadcast a message to all connected clients
+ ///
+ /// The message to broadcast
+ Task BroadcastMessageAsync(string message);
+}
diff --git a/src/CMSMicroservice.Application/Profiles/CityProfile.cs b/src/CMSMicroservice.Application/Profiles/CityProfile.cs
new file mode 100644
index 0000000..92cf0a0
--- /dev/null
+++ b/src/CMSMicroservice.Application/Profiles/CityProfile.cs
@@ -0,0 +1,22 @@
+using CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
+using CMSMicroservice.Domain.Entities.Geography;
+using Mapster;
+
+namespace CMSMicroservice.Application.Profiles;
+
+public class CityProfile : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ config.NewConfig()
+ .Map(dest => dest.Id, src => src.Id)
+ .Map(dest => dest.ExternalId, src => src.ExternalId)
+ .Map(dest => dest.Name, src => src.Name)
+ .Map(dest => dest.Native, src => src.Native)
+ .Map(dest => dest.Latitude, src => src.Latitude)
+ .Map(dest => dest.Longitude, src => src.Longitude)
+ .Map(dest => dest.StateId, src => src.StateId)
+ .Map(dest => dest.StateName, src => src.State.Name)
+ .Map(dest => dest.StateNative, src => src.State.Native);
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommand.cs b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommand.cs
new file mode 100644
index 0000000..af4d0f2
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommand.cs
@@ -0,0 +1,14 @@
+using MediatR;
+
+namespace CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
+
+///
+/// درخواست رفرش توکن JWT
+///
+public sealed record RefreshTokenCommand : IRequest
+{
+ ///
+ /// توکن فعلی کاربر
+ ///
+ public string CurrentToken { get; init; } = null!;
+}
diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommandHandler.cs
new file mode 100644
index 0000000..57191e8
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenCommandHandler.cs
@@ -0,0 +1,82 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Entities;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
+
+///
+/// هندلر رفرش توکن - توکن جدید با آخرین اطلاعات کاربر تولید میکند
+///
+public class RefreshTokenCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly IGenerateJwtToken _generateJwt;
+
+ public RefreshTokenCommandHandler(IApplicationDbContext context, IGenerateJwtToken generateJwt)
+ {
+ _context = context;
+ _generateJwt = generateJwt;
+ }
+
+ public async Task Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // Extract user id from current token
+ var handler = new JwtSecurityTokenHandler();
+ var jwtToken = handler.ReadJwtToken(request.CurrentToken);
+
+ var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
+ if (userIdClaim == null || !long.TryParse(userIdClaim.Value, out var userId))
+ {
+ return new RefreshTokenResponseDto
+ {
+ Success = false,
+ Message = "توکن نامعتبر است",
+ Token = string.Empty
+ };
+ }
+
+ // Get user with all required relations
+ var user = await _context.Users
+ .Include(u => u.UserContracts)
+ .ThenInclude(u => u.Contract)
+ .Include(u => u.UserRoles)
+ .ThenInclude(ur => ur.Role)
+ .Include(u => u.ClubMembership)
+ .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
+
+ if (user == null)
+ {
+ return new RefreshTokenResponseDto
+ {
+ Success = false,
+ Message = "کاربر یافت نشد",
+ Token = string.Empty
+ };
+ }
+
+ // Generate new token
+ var newToken = await _generateJwt.GenerateJwtToken(user);
+
+ return new RefreshTokenResponseDto
+ {
+ Success = true,
+ Message = "توکن با موفقیت رفرش شد",
+ Token = newToken
+ };
+ }
+ catch (Exception ex)
+ {
+ return new RefreshTokenResponseDto
+ {
+ Success = false,
+ Message = $"خطا در رفرش توکن: {ex.Message}",
+ Token = string.Empty
+ };
+ }
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenResponseDto.cs
new file mode 100644
index 0000000..a868eae
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserCQ/Commands/RefreshToken/RefreshTokenResponseDto.cs
@@ -0,0 +1,22 @@
+namespace CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
+
+///
+/// پاسخ رفرش توکن
+///
+public class RefreshTokenResponseDto
+{
+ ///
+ /// توکن جدید
+ ///
+ public string Token { get; set; } = null!;
+
+ ///
+ /// آیا عملیات موفق بود؟
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// پیام
+ ///
+ public string Message { get; set; } = null!;
+}
diff --git a/src/CMSMicroservice.Application/UserCQ/EventHandlers/UpdateUserEventHandlers/UpdateUserEventHandler.cs b/src/CMSMicroservice.Application/UserCQ/EventHandlers/UpdateUserEventHandlers/UpdateUserEventHandler.cs
index 45714ae..cdefea6 100644
--- a/src/CMSMicroservice.Application/UserCQ/EventHandlers/UpdateUserEventHandlers/UpdateUserEventHandler.cs
+++ b/src/CMSMicroservice.Application/UserCQ/EventHandlers/UpdateUserEventHandlers/UpdateUserEventHandler.cs
@@ -1,3 +1,4 @@
+using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Logging;
@@ -6,16 +7,23 @@ namespace CMSMicroservice.Application.UserCQ.EventHandlers;
public class UpdateUserEventHandler : INotificationHandler
{
private readonly ILogger _logger;
+ private readonly ITokenNotificationService _tokenNotificationService;
- public UpdateUserEventHandler(ILogger logger)
+ public UpdateUserEventHandler(
+ ILogger logger,
+ ITokenNotificationService tokenNotificationService)
{
_logger = logger;
+ _tokenNotificationService = tokenNotificationService;
}
- public Task Handle(UpdateUserEvent notification, CancellationToken cancellationToken)
+ public async Task Handle(UpdateUserEvent notification, CancellationToken cancellationToken)
{
- _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
+ _logger.LogInformation("Domain Event: {DomainEvent} for User {UserId}",
+ notification.GetType().Name,
+ notification.Item.Id);
- return Task.CompletedTask;
+ // Notify connected clients that user profile has changed and token should be refreshed
+ await _tokenNotificationService.NotifyForceRefreshAsync(notification.Item.Id);
}
}
diff --git a/src/CMSMicroservice.Domain/Entities/Geography/City.cs b/src/CMSMicroservice.Domain/Entities/Geography/City.cs
new file mode 100644
index 0000000..386dbd5
--- /dev/null
+++ b/src/CMSMicroservice.Domain/Entities/Geography/City.cs
@@ -0,0 +1,49 @@
+using CMSMicroservice.Domain.Common;
+
+namespace CMSMicroservice.Domain.Entities.Geography;
+
+///
+/// شهر - City
+///
+public class City : BaseAuditableEntity
+{
+ ///
+ /// شناسه یکتا
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// شناسه خارجی (External ID)
+ ///
+ public long ExternalId { get; set; }
+
+ ///
+ /// نام شهر
+ ///
+ public required string Name { get; set; }
+
+ ///
+ /// عرض جغرافیایی
+ ///
+ public required string Latitude { get; set; }
+
+ ///
+ /// طول جغرافیایی
+ ///
+ public required string Longitude { get; set; }
+
+ ///
+ /// نام بومی شهر (فارسی یا زبان محلی)
+ ///
+ public required string Native { get; set; }
+
+ ///
+ /// شناسه استان متعلق
+ ///
+ public long StateId { get; set; }
+
+ ///
+ /// استان متعلق
+ ///
+ public virtual State State { get; set; } = null!;
+}
diff --git a/src/CMSMicroservice.Domain/Entities/Geography/Country.cs b/src/CMSMicroservice.Domain/Entities/Geography/Country.cs
new file mode 100644
index 0000000..7393371
--- /dev/null
+++ b/src/CMSMicroservice.Domain/Entities/Geography/Country.cs
@@ -0,0 +1,109 @@
+using CMSMicroservice.Domain.Common;
+
+namespace CMSMicroservice.Domain.Entities.Geography;
+
+///
+/// کشور - Country
+///
+public class Country : BaseAuditableEntity
+{
+ ///
+ /// شناسه یکتا
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// شناسه خارجی (External ID)
+ ///
+ public long ExternalId { get; set; }
+
+ ///
+ /// نام کشور (انگلیسی)
+ ///
+ public required string Name { get; set; }
+
+ ///
+ /// کد ISO3 (3 حرفی)
+ ///
+ public required string Iso3 { get; set; }
+
+ ///
+ /// کد ISO2 (2 حرفی)
+ ///
+ public required string Iso2 { get; set; }
+
+ ///
+ /// کد عددی کشور
+ ///
+ public required string NumericCode { get; set; }
+
+ ///
+ /// کد تلفن کشور (مثلاً 98 برای ایران)
+ ///
+ public required string PhoneCode { get; set; }
+
+ ///
+ /// پایتخت
+ ///
+ public required string Capital { get; set; }
+
+ ///
+ /// واحد پول (کد ارز)
+ ///
+ public required string Currency { get; set; }
+
+ ///
+ /// نام واحد پول
+ ///
+ public required string CurrencyName { get; set; }
+
+ ///
+ /// نماد واحد پول
+ ///
+ public required string CurrencySymbol { get; set; }
+
+ ///
+ /// Top-Level Domain (مثلاً .ir)
+ ///
+ public required string Tld { get; set; }
+
+ ///
+ /// نام بومی کشور (فارسی یا زبان محلی)
+ ///
+ public required string Native { get; set; }
+
+ ///
+ /// منطقه جغرافیایی (مثلاً Asia)
+ ///
+ public required string Region { get; set; }
+
+ ///
+ /// زیرمنطقه (مثلاً Southern Asia)
+ ///
+ public required string Subregion { get; set; }
+
+ ///
+ /// عرض جغرافیایی
+ ///
+ public required string Latitude { get; set; }
+
+ ///
+ /// طول جغرافیایی
+ ///
+ public required string Longitude { get; set; }
+
+ ///
+ /// ایموجی پرچم کشور
+ ///
+ public required string Emoji { get; set; }
+
+ ///
+ /// کد یونیکد ایموجی
+ ///
+ public required string EmojiU { get; set; }
+
+ ///
+ /// استانهای این کشور
+ ///
+ public virtual ICollection States { get; set; } = new List();
+}
diff --git a/src/CMSMicroservice.Domain/Entities/Geography/State.cs b/src/CMSMicroservice.Domain/Entities/Geography/State.cs
new file mode 100644
index 0000000..ba6cf44
--- /dev/null
+++ b/src/CMSMicroservice.Domain/Entities/Geography/State.cs
@@ -0,0 +1,64 @@
+using CMSMicroservice.Domain.Common;
+
+namespace CMSMicroservice.Domain.Entities.Geography;
+
+///
+/// استان - State/Province
+///
+public class State : BaseAuditableEntity
+{
+ ///
+ /// شناسه یکتا
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// شناسه خارجی (External ID)
+ ///
+ public long ExternalId { get; set; }
+
+ ///
+ /// نام استان
+ ///
+ public required string Name { get; set; }
+
+ ///
+ /// کد استان
+ ///
+ public required string StateCode { get; set; }
+
+ ///
+ /// عرض جغرافیایی
+ ///
+ public required string Latitude { get; set; }
+
+ ///
+ /// طول جغرافیایی
+ ///
+ public required string Longitude { get; set; }
+
+ ///
+ /// نوع استان (province, state, etc.)
+ ///
+ public required string Type { get; set; }
+
+ ///
+ /// نام بومی استان (فارسی یا زبان محلی)
+ ///
+ public required string Native { get; set; }
+
+ ///
+ /// شناسه کشور متعلق
+ ///
+ public long CountryId { get; set; }
+
+ ///
+ /// کشور متعلق
+ ///
+ public virtual Country Country { get; set; } = null!;
+
+ ///
+ /// شهرهای این استان
+ ///
+ public virtual ICollection Cities { get; set; } = new List();
+}
diff --git a/src/CMSMicroservice.Domain/Enums/ContractType.cs b/src/CMSMicroservice.Domain/Enums/ContractType.cs
index 401dadd..4a239c8 100644
--- a/src/CMSMicroservice.Domain/Enums/ContractType.cs
+++ b/src/CMSMicroservice.Domain/Enums/ContractType.cs
@@ -2,6 +2,6 @@ namespace CMSMicroservice.Domain.Enums;
//قراردادها
public enum ContractType
{
- Main = 0,
- CMS = 1,
+ Main = 0, // قرارداد ثبتنام اولیه
+ ClubMembership = 1, // قرارداد باشگاه مشتریان
}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
index 9f42582..1bf1274 100644
--- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
+++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
@@ -3,7 +3,7 @@ using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.EntityFrameworkCore.Diagnostics;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Payment;
-
+using CMSMicroservice.Domain.Entities.Geography;
using CMSMicroservice.Domain.Entities.Order;
using CMSMicroservice.Domain.Entities.DiscountShop;
using CMSMicroservice.Infrastructure.Persistence.Interceptors;
@@ -111,4 +111,9 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
public DbSet DiscountShoppingCarts => Set();
public DbSet DiscountOrders => Set();
public DbSet DiscountOrderDetails => Set();
+
+ // ============= Geography DbSets =============
+ public DbSet Countries => Set();
+ public DbSet States => Set();
+ public DbSet Cities => Set();
}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CityConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CityConfiguration.cs
new file mode 100644
index 0000000..8a2fa7f
--- /dev/null
+++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CityConfiguration.cs
@@ -0,0 +1,55 @@
+using CMSMicroservice.Domain.Entities.Geography;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Geography;
+
+public class CityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Cities", "GMS");
+
+ builder.HasKey(c => c.Id);
+
+ builder.Property(c => c.Id)
+ .ValueGeneratedOnAdd();
+
+ builder.Property(c => c.ExternalId)
+ .IsRequired();
+
+ builder.Property(c => c.Name)
+ .IsRequired()
+ .HasMaxLength(500);
+
+ builder.Property(c => c.Latitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(c => c.Longitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(c => c.Native)
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasDefaultValue("");
+
+ builder.Property(c => c.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false);
+
+ builder.Property(c => c.StateId)
+ .IsRequired();
+
+ // Index
+ builder.HasIndex(c => c.StateId)
+ .HasDatabaseName("IX_Cities_StateId");
+
+ // Relationships
+ builder.HasOne(c => c.State)
+ .WithMany(s => s.Cities)
+ .HasForeignKey(c => c.StateId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CountryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CountryConfiguration.cs
new file mode 100644
index 0000000..3f63178
--- /dev/null
+++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/CountryConfiguration.cs
@@ -0,0 +1,99 @@
+using CMSMicroservice.Domain.Entities.Geography;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Geography;
+
+public class CountryConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Countries", "GMS");
+
+ builder.HasKey(c => c.Id);
+
+ builder.Property(c => c.Id)
+ .ValueGeneratedOnAdd();
+
+ builder.Property(c => c.ExternalId)
+ .IsRequired();
+
+ builder.Property(c => c.Name)
+ .IsRequired()
+ .HasMaxLength(500);
+
+ builder.Property(c => c.Iso3)
+ .IsRequired()
+ .HasMaxLength(3);
+
+ builder.Property(c => c.Iso2)
+ .IsRequired()
+ .HasMaxLength(2);
+
+ builder.Property(c => c.NumericCode)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.PhoneCode)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.Capital)
+ .IsRequired()
+ .HasMaxLength(500);
+
+ builder.Property(c => c.Currency)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.CurrencyName)
+ .IsRequired()
+ .HasMaxLength(100);
+
+ builder.Property(c => c.CurrencySymbol)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.Tld)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.Native)
+ .IsRequired()
+ .HasMaxLength(500);
+
+ builder.Property(c => c.Region)
+ .IsRequired()
+ .HasMaxLength(100);
+
+ builder.Property(c => c.Subregion)
+ .IsRequired()
+ .HasMaxLength(100);
+
+ builder.Property(c => c.Latitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(c => c.Longitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(c => c.Emoji)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(c => c.EmojiU)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(c => c.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false);
+
+ // Relationships
+ builder.HasMany(c => c.States)
+ .WithOne(s => s.Country)
+ .HasForeignKey(s => s.CountryId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/StateConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/StateConfiguration.cs
new file mode 100644
index 0000000..ab15efe
--- /dev/null
+++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Geography/StateConfiguration.cs
@@ -0,0 +1,68 @@
+using CMSMicroservice.Domain.Entities.Geography;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Geography;
+
+public class StateConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("States", "GMS");
+
+ builder.HasKey(s => s.Id);
+
+ builder.Property(s => s.Id)
+ .ValueGeneratedOnAdd();
+
+ builder.Property(s => s.ExternalId)
+ .IsRequired();
+
+ builder.Property(s => s.Name)
+ .IsRequired()
+ .HasMaxLength(500);
+
+ builder.Property(s => s.StateCode)
+ .IsRequired()
+ .HasMaxLength(10);
+
+ builder.Property(s => s.Latitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(s => s.Longitude)
+ .IsRequired()
+ .HasMaxLength(50);
+
+ builder.Property(s => s.Type)
+ .IsRequired()
+ .HasMaxLength(100);
+
+ builder.Property(s => s.Native)
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasDefaultValue("");
+
+ builder.Property(s => s.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false);
+
+ builder.Property(s => s.CountryId)
+ .IsRequired();
+
+ // Index
+ builder.HasIndex(s => s.CountryId)
+ .HasDatabaseName("IX_States_CountryId");
+
+ // Relationships
+ builder.HasOne(s => s.Country)
+ .WithMany(c => c.States)
+ .HasForeignKey(s => s.CountryId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ builder.HasMany(s => s.Cities)
+ .WithOne(c => c.State)
+ .HasForeignKey(c => c.StateId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251216190010_AddGeographyEntities.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251216190010_AddGeographyEntities.Designer.cs
new file mode 100644
index 0000000..293cb6b
--- /dev/null
+++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251216190010_AddGeographyEntities.Designer.cs
@@ -0,0 +1,3523 @@
+//
+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("20251216190010_AddGeographyEntities")]
+ partial class AddGeographyEntities
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("CMS")
+ .HasAnnotation("ProductVersion", "9.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ImagePath")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ParentId")
+ .HasColumnType("bigint");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ParentId");
+
+ b.ToTable("Categories", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RequiredPoints")
+ .HasColumnType("int");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsActive", "SortOrder")
+ .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder");
+
+ b.ToTable("ClubFeatures", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ActivatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("GiftValue")
+ .HasColumnType("bigint");
+
+ b.Property("InitialContribution")
+ .HasColumnType("bigint");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PurchaseMethod")
+ .HasColumnType("int");
+
+ b.Property("TotalEarned")
+ .HasColumnType("bigint");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsActive")
+ .HasDatabaseName("IX_ClubMembership_IsActive");
+
+ b.HasIndex("UserId")
+ .IsUnique()
+ .HasDatabaseName("IX_ClubMembership_UserId");
+
+ b.ToTable("ClubMemberships", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClubFeatureId")
+ .HasColumnType("bigint");
+
+ b.Property("ClubMembershipId")
+ .HasColumnType("bigint");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("GrantedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true);
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Notes")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClubFeatureId");
+
+ b.HasIndex("ClubMembershipId")
+ .HasDatabaseName("IX_UserClubFeature_ClubMembershipId");
+
+ b.HasIndex("UserId", "ClubFeatureId")
+ .IsUnique()
+ .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId");
+
+ b.ToTable("UserClubFeatures", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BalancesEarned")
+ .HasColumnType("int");
+
+ b.Property("BankReferenceId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BankTrackingCode")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IbanNumber")
+ .HasMaxLength(26)
+ .HasColumnType("nvarchar(26)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PaidAt")
+ .HasColumnType("datetime2");
+
+ b.Property("PaymentFailureReason")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ProcessedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ProcessedBy")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("RejectionReason")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("TotalAmount")
+ .HasColumnType("bigint");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.Property("ValuePerBalance")
+ .HasColumnType("bigint");
+
+ b.Property("WeekNumber")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("nvarchar(20)");
+
+ b.Property("WeeklyPoolId")
+ .HasColumnType("bigint");
+
+ b.Property("WithdrawalMethod")
+ .HasColumnType("int");
+
+ b.Property("WithdrawnAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_UserCommissionPayout_Status");
+
+ b.HasIndex("WeekNumber")
+ .HasDatabaseName("IX_UserCommissionPayout_WeekNumber");
+
+ b.HasIndex("WeeklyPoolId")
+ .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId");
+
+ b.HasIndex("UserId", "WeekNumber")
+ .IsUnique()
+ .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekNumber");
+
+ b.ToTable("UserCommissionPayouts", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CalculatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsCalculated")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TotalBalances")
+ .HasColumnType("int");
+
+ b.Property("TotalPoolAmount")
+ .HasColumnType("bigint");
+
+ b.Property("ValuePerBalance")
+ .HasColumnType("bigint");
+
+ b.Property("WeekNumber")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("nvarchar(20)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsCalculated")
+ .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated");
+
+ b.HasIndex("WeekNumber")
+ .IsUnique()
+ .HasDatabaseName("IX_WeeklyCommissionPool_WeekNumber");
+
+ b.ToTable("WeeklyCommissionPools", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CompletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Details")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DurationMs")
+ .HasColumnType("bigint");
+
+ b.Property("ErrorCount")
+ .HasColumnType("int");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ErrorStackTrace")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ExecutionId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ProcessedCount")
+ .HasColumnType("int");
+
+ b.Property("StartedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("WeekNumber")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("StartedAt");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("WeekNumber");
+
+ b.ToTable("WorkerExecutionLogs", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.SystemConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DataType")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Description")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Scope")
+ .HasColumnType("int");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsActive")
+ .HasDatabaseName("IX_SystemConfiguration_IsActive");
+
+ b.HasIndex("Scope", "Key")
+ .IsUnique()
+ .HasDatabaseName("IX_SystemConfiguration_Scope_Key");
+
+ b.ToTable("SystemConfigurations", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HtmlContent")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("Contracts", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ContractNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("IsProcessed")
+ .HasColumnType("bit");
+
+ b.Property("LastCheckDate")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NationalCode")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ProcessedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("TransactionId")
+ .HasColumnType("bigint");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TransactionId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DayaLoanContracts", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("ImagePath")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true);
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ParentCategoryId")
+ .HasColumnType("bigint");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ParentCategoryId");
+
+ b.ToTable("DiscountCategories", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeliveryDescription")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("DeliveryStatus")
+ .HasColumnType("int");
+
+ b.Property("DiscountBalanceUsed")
+ .HasColumnType("bigint");
+
+ b.Property("GatewayAmountPaid")
+ .HasColumnType("bigint");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PaymentDate")
+ .HasColumnType("datetime2");
+
+ b.Property("PaymentStatus")
+ .HasColumnType("int");
+
+ b.Property("TotalAmount")
+ .HasColumnType("bigint");
+
+ b.Property("TrackingCode")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("TransactionId")
+ .HasColumnType("bigint");
+
+ b.Property("UserAddressId")
+ .HasColumnType("bigint");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.Property("VatAmount")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TransactionId");
+
+ b.HasIndex("UserAddressId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DiscountOrders", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Count")
+ .HasColumnType("int");
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DiscountAmount")
+ .HasColumnType("bigint");
+
+ b.Property("DiscountOrderId")
+ .HasColumnType("bigint");
+
+ b.Property("DiscountPercentUsed")
+ .HasColumnType("int");
+
+ b.Property("FinalPrice")
+ .HasColumnType("bigint");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ProductId")
+ .HasColumnType("bigint");
+
+ b.Property("UnitPrice")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DiscountOrderId");
+
+ b.HasIndex("ProductId");
+
+ b.ToTable("DiscountOrderDetails", "CMS");
+ });
+
+ modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FullInformation")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ImagePath")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true);
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property