feat: add geography entities with countries, states and cities
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.4.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.10" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||
|
||||
public record GetAllCitiesByFilterQuery : IRequest<GetAllCitiesByFilterResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// موقعیت صفحه بندی
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتب سازی بر اساس
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر
|
||||
/// </summary>
|
||||
public GetAllCitiesByFilterFilter? Filter { get; init; }
|
||||
}
|
||||
|
||||
public class GetAllCitiesByFilterFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه
|
||||
/// </summary>
|
||||
public long? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام شهر (Contains)
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام بومی شهر (Contains)
|
||||
/// </summary>
|
||||
public string? Native { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه استان
|
||||
/// </summary>
|
||||
public long? StateId { get; set; }
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||
|
||||
public class GetAllCitiesByFilterQueryHandler : IRequestHandler<GetAllCitiesByFilterQuery, GetAllCitiesByFilterResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllCitiesByFilterQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllCitiesByFilterResponseDto> 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<GetAllCitiesByFilterResponseModel>()
|
||||
.ToListAsync(cancellationToken)
|
||||
};
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||
|
||||
public class GetAllCitiesByFilterQueryValidator : AbstractValidator<GetAllCitiesByFilterQuery>
|
||||
{
|
||||
public GetAllCitiesByFilterQueryValidator()
|
||||
{
|
||||
// Validation rules اگر نیاز باشد
|
||||
}
|
||||
|
||||
public async Task<bool> Validate(object model, string propertyName)
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetAllCitiesByFilterQuery>.CreateWithOptions(
|
||||
(GetAllCitiesByFilterQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
return result.IsValid;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
namespace CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||
|
||||
public class GetAllCitiesByFilterResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// متادیتا
|
||||
/// </summary>
|
||||
public MetaData MetaData { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// مدل خروجی
|
||||
/// </summary>
|
||||
public List<GetAllCitiesByFilterResponseModel>? Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetAllCitiesByFilterResponseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه خارجی
|
||||
/// </summary>
|
||||
public long ExternalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام شهر
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// نام بومی شهر (فارسی)
|
||||
/// </summary>
|
||||
public string Native { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// عرض جغرافیایی
|
||||
/// </summary>
|
||||
public string Latitude { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// طول جغرافیایی
|
||||
/// </summary>
|
||||
public string Longitude { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// شناسه استان
|
||||
/// </summary>
|
||||
public long StateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام استان
|
||||
/// </summary>
|
||||
public string StateName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// نام بومی استان
|
||||
/// </summary>
|
||||
public string StateNative { get; set; } = null!;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای پذیرش قرارداد باشگاه مشتریان
|
||||
/// </summary>
|
||||
public record AcceptClubMembershipContractCommand : IRequest<AcceptClubMembershipContractResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// کد OTP برای تایید
|
||||
/// </summary>
|
||||
public string OtpCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه یکتای امضا (GUID)
|
||||
/// </summary>
|
||||
public string SignGuid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// محتوای HTML قرارداد
|
||||
/// </summary>
|
||||
public string ContractHtml { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO پاسخ پذیرش قرارداد باشگاه مشتریان
|
||||
/// </summary>
|
||||
public class AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// آیا عملیات موفق بود؟
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام نتیجه
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه قرارداد ثبت شده
|
||||
/// </summary>
|
||||
public long ContractId { get; set; }
|
||||
}
|
||||
+196
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای پذیرش قرارداد باشگاه مشتریان
|
||||
/// این handler:
|
||||
/// 1. کد OTP را تایید میکند
|
||||
/// 2. قرارداد را در جدول UserContract ثبت میکند
|
||||
/// 3. باشگاه مشتری را فعال میکند (IsActive = true)
|
||||
/// </summary>
|
||||
public class AcceptClubMembershipContractCommandHandler
|
||||
: IRequestHandler<AcceptClubMembershipContractCommand, AcceptClubMembershipContractResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IConfiguration _cfg;
|
||||
private readonly IHashService _hashService;
|
||||
private readonly ILogger<AcceptClubMembershipContractCommandHandler> _logger;
|
||||
|
||||
private const int MaxAttempts = 5;
|
||||
private const string OtpPurpose = "signClubContract";
|
||||
|
||||
public AcceptClubMembershipContractCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IConfiguration cfg,
|
||||
IHashService hashService,
|
||||
ILogger<AcceptClubMembershipContractCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
_hashService = hashService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AcceptClubMembershipContractResponseDto> 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, "کد تایید صحیح است");
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
|
||||
public class AcceptClubMembershipContractCommandValidator : AbstractValidator<AcceptClubMembershipContractCommand>
|
||||
{
|
||||
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("محتوای قرارداد الزامی است");
|
||||
}
|
||||
}
|
||||
@@ -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<DiscountOrder> DiscountOrders { get; }
|
||||
DbSet<DiscountOrderDetail> DiscountOrderDetails { get; }
|
||||
|
||||
// ============= Geography =============
|
||||
DbSet<Country> Countries { get; }
|
||||
DbSet<State> States { get; }
|
||||
DbSet<City> Cities { get; }
|
||||
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for sending token-related notifications via SignalR
|
||||
/// </summary>
|
||||
public interface ITokenNotificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Notify that a user's token has been revoked and they should refresh their token
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID whose token was revoked</param>
|
||||
/// <param name="reason">The reason for revocation</param>
|
||||
Task NotifyTokenRevokedAsync(long userId, string reason);
|
||||
|
||||
/// <summary>
|
||||
/// Notify that a user should refresh their token due to profile changes
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID whose profile changed</param>
|
||||
Task NotifyForceRefreshAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast a message to all connected clients
|
||||
/// </summary>
|
||||
/// <param name="message">The message to broadcast</param>
|
||||
Task BroadcastMessageAsync(string message);
|
||||
}
|
||||
@@ -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<City, GetAllCitiesByFilterResponseModel>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// درخواست رفرش توکن JWT
|
||||
/// </summary>
|
||||
public sealed record RefreshTokenCommand : IRequest<RefreshTokenResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// توکن فعلی کاربر
|
||||
/// </summary>
|
||||
public string CurrentToken { get; init; } = null!;
|
||||
}
|
||||
+82
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// هندلر رفرش توکن - توکن جدید با آخرین اطلاعات کاربر تولید میکند
|
||||
/// </summary>
|
||||
public class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, RefreshTokenResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IGenerateJwtToken _generateJwt;
|
||||
|
||||
public RefreshTokenCommandHandler(IApplicationDbContext context, IGenerateJwtToken generateJwt)
|
||||
{
|
||||
_context = context;
|
||||
_generateJwt = generateJwt;
|
||||
}
|
||||
|
||||
public async Task<RefreshTokenResponseDto> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
namespace CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// پاسخ رفرش توکن
|
||||
/// </summary>
|
||||
public class RefreshTokenResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// توکن جدید
|
||||
/// </summary>
|
||||
public string Token { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// آیا عملیات موفق بود؟
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام
|
||||
/// </summary>
|
||||
public string Message { get; set; } = null!;
|
||||
}
|
||||
+12
-4
@@ -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<UpdateUserEvent>
|
||||
{
|
||||
private readonly ILogger<UpdateUserEventHandler> _logger;
|
||||
private readonly ITokenNotificationService _tokenNotificationService;
|
||||
|
||||
public UpdateUserEventHandler(ILogger<UpdateUserEventHandler> logger)
|
||||
public UpdateUserEventHandler(
|
||||
ILogger<UpdateUserEventHandler> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user