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="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" 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" />
|
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.10" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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.Payment;
|
||||||
using CMSMicroservice.Domain.Entities.Order;
|
using CMSMicroservice.Domain.Entities.Order;
|
||||||
using CMSMicroservice.Domain.Entities.DiscountShop;
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using CMSMicroservice.Domain.Entities.Geography;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
|
||||||
@@ -53,5 +54,10 @@ public interface IApplicationDbContext
|
|||||||
DbSet<DiscountOrder> DiscountOrders { get; }
|
DbSet<DiscountOrder> DiscountOrders { get; }
|
||||||
DbSet<DiscountOrderDetail> DiscountOrderDetails { get; }
|
DbSet<DiscountOrderDetail> DiscountOrderDetails { get; }
|
||||||
|
|
||||||
|
// ============= Geography =============
|
||||||
|
DbSet<Country> Countries { get; }
|
||||||
|
DbSet<State> States { get; }
|
||||||
|
DbSet<City> Cities { get; }
|
||||||
|
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
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 CMSMicroservice.Domain.Events;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -6,16 +7,23 @@ namespace CMSMicroservice.Application.UserCQ.EventHandlers;
|
|||||||
public class UpdateUserEventHandler : INotificationHandler<UpdateUserEvent>
|
public class UpdateUserEventHandler : INotificationHandler<UpdateUserEvent>
|
||||||
{
|
{
|
||||||
private readonly ILogger<UpdateUserEventHandler> _logger;
|
private readonly ILogger<UpdateUserEventHandler> _logger;
|
||||||
|
private readonly ITokenNotificationService _tokenNotificationService;
|
||||||
|
|
||||||
public UpdateUserEventHandler(ILogger<UpdateUserEventHandler> logger)
|
public UpdateUserEventHandler(
|
||||||
|
ILogger<UpdateUserEventHandler> logger,
|
||||||
|
ITokenNotificationService tokenNotificationService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using CMSMicroservice.Domain.Common;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Domain.Entities.Geography;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شهر - City
|
||||||
|
/// </summary>
|
||||||
|
public class City : BaseAuditableEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه یکتا
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه خارجی (External ID)
|
||||||
|
/// </summary>
|
||||||
|
public long ExternalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام شهر
|
||||||
|
/// </summary>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// عرض جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Latitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// طول جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Longitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام بومی شهر (فارسی یا زبان محلی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Native { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه استان متعلق
|
||||||
|
/// </summary>
|
||||||
|
public long StateId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// استان متعلق
|
||||||
|
/// </summary>
|
||||||
|
public virtual State State { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
using CMSMicroservice.Domain.Common;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Domain.Entities.Geography;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کشور - Country
|
||||||
|
/// </summary>
|
||||||
|
public class Country : BaseAuditableEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه یکتا
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه خارجی (External ID)
|
||||||
|
/// </summary>
|
||||||
|
public long ExternalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام کشور (انگلیسی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد ISO3 (3 حرفی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Iso3 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد ISO2 (2 حرفی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Iso2 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد عددی کشور
|
||||||
|
/// </summary>
|
||||||
|
public required string NumericCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد تلفن کشور (مثلاً 98 برای ایران)
|
||||||
|
/// </summary>
|
||||||
|
public required string PhoneCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پایتخت
|
||||||
|
/// </summary>
|
||||||
|
public required string Capital { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// واحد پول (کد ارز)
|
||||||
|
/// </summary>
|
||||||
|
public required string Currency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام واحد پول
|
||||||
|
/// </summary>
|
||||||
|
public required string CurrencyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نماد واحد پول
|
||||||
|
/// </summary>
|
||||||
|
public required string CurrencySymbol { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Top-Level Domain (مثلاً .ir)
|
||||||
|
/// </summary>
|
||||||
|
public required string Tld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام بومی کشور (فارسی یا زبان محلی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Native { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// منطقه جغرافیایی (مثلاً Asia)
|
||||||
|
/// </summary>
|
||||||
|
public required string Region { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// زیرمنطقه (مثلاً Southern Asia)
|
||||||
|
/// </summary>
|
||||||
|
public required string Subregion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// عرض جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Latitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// طول جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Longitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ایموجی پرچم کشور
|
||||||
|
/// </summary>
|
||||||
|
public required string Emoji { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد یونیکد ایموجی
|
||||||
|
/// </summary>
|
||||||
|
public required string EmojiU { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// استانهای این کشور
|
||||||
|
/// </summary>
|
||||||
|
public virtual ICollection<State> States { get; set; } = new List<State>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using CMSMicroservice.Domain.Common;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Domain.Entities.Geography;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// استان - State/Province
|
||||||
|
/// </summary>
|
||||||
|
public class State : BaseAuditableEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه یکتا
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه خارجی (External ID)
|
||||||
|
/// </summary>
|
||||||
|
public long ExternalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام استان
|
||||||
|
/// </summary>
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد استان
|
||||||
|
/// </summary>
|
||||||
|
public required string StateCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// عرض جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Latitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// طول جغرافیایی
|
||||||
|
/// </summary>
|
||||||
|
public required string Longitude { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع استان (province, state, etc.)
|
||||||
|
/// </summary>
|
||||||
|
public required string Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام بومی استان (فارسی یا زبان محلی)
|
||||||
|
/// </summary>
|
||||||
|
public required string Native { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه کشور متعلق
|
||||||
|
/// </summary>
|
||||||
|
public long CountryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کشور متعلق
|
||||||
|
/// </summary>
|
||||||
|
public virtual Country Country { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شهرهای این استان
|
||||||
|
/// </summary>
|
||||||
|
public virtual ICollection<City> Cities { get; set; } = new List<City>();
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@ namespace CMSMicroservice.Domain.Enums;
|
|||||||
//قراردادها
|
//قراردادها
|
||||||
public enum ContractType
|
public enum ContractType
|
||||||
{
|
{
|
||||||
Main = 0,
|
Main = 0, // قرارداد ثبتنام اولیه
|
||||||
CMS = 1,
|
ClubMembership = 1, // قرارداد باشگاه مشتریان
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using CMSMicroservice.Application.Common.Interfaces;
|
|||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using CMSMicroservice.Domain.Entities;
|
using CMSMicroservice.Domain.Entities;
|
||||||
using CMSMicroservice.Domain.Entities.Payment;
|
using CMSMicroservice.Domain.Entities.Payment;
|
||||||
|
using CMSMicroservice.Domain.Entities.Geography;
|
||||||
using CMSMicroservice.Domain.Entities.Order;
|
using CMSMicroservice.Domain.Entities.Order;
|
||||||
using CMSMicroservice.Domain.Entities.DiscountShop;
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
using CMSMicroservice.Infrastructure.Persistence.Interceptors;
|
using CMSMicroservice.Infrastructure.Persistence.Interceptors;
|
||||||
@@ -111,4 +111,9 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
|
|||||||
public DbSet<DiscountShoppingCart> DiscountShoppingCarts => Set<DiscountShoppingCart>();
|
public DbSet<DiscountShoppingCart> DiscountShoppingCarts => Set<DiscountShoppingCart>();
|
||||||
public DbSet<DiscountOrder> DiscountOrders => Set<DiscountOrder>();
|
public DbSet<DiscountOrder> DiscountOrders => Set<DiscountOrder>();
|
||||||
public DbSet<DiscountOrderDetail> DiscountOrderDetails => Set<DiscountOrderDetail>();
|
public DbSet<DiscountOrderDetail> DiscountOrderDetails => Set<DiscountOrderDetail>();
|
||||||
|
|
||||||
|
// ============= Geography DbSets =============
|
||||||
|
public DbSet<Country> Countries => Set<Country>();
|
||||||
|
public DbSet<State> States => Set<State>();
|
||||||
|
public DbSet<City> Cities => Set<City>();
|
||||||
}
|
}
|
||||||
|
|||||||
+55
@@ -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<City>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<City> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
@@ -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<Country>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Country> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -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<State>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<State> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3523
File diff suppressed because it is too large
Load Diff
+146
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddGeographyEntities : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.EnsureSchema(
|
||||||
|
name: "GMS");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Countries",
|
||||||
|
schema: "GMS",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
ExternalId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
Iso3 = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: false),
|
||||||
|
Iso2 = table.Column<string>(type: "nvarchar(2)", maxLength: 2, nullable: false),
|
||||||
|
NumericCode = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
PhoneCode = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
Capital = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
Currency = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
CurrencyName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
CurrencySymbol = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
Tld = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
Native = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
Region = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
Subregion = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
Latitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Longitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Emoji = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
EmojiU = table.Column<string>(type: "nvarchar(50)", maxLength: 50, 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, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Countries", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "States",
|
||||||
|
schema: "GMS",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
ExternalId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
StateCode = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
|
Latitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Longitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Type = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
Native = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||||
|
CountryId = table.Column<long>(type: "bigint", 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, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_States", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_States_Countries_CountryId",
|
||||||
|
column: x => x.CountryId,
|
||||||
|
principalSchema: "GMS",
|
||||||
|
principalTable: "Countries",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Cities",
|
||||||
|
schema: "GMS",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
ExternalId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
Latitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Longitude = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Native = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||||
|
StateId = table.Column<long>(type: "bigint", 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, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Cities", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Cities_States_StateId",
|
||||||
|
column: x => x.StateId,
|
||||||
|
principalSchema: "GMS",
|
||||||
|
principalTable: "States",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Cities_StateId",
|
||||||
|
schema: "GMS",
|
||||||
|
table: "Cities",
|
||||||
|
column: "StateId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_States_CountryId",
|
||||||
|
schema: "GMS",
|
||||||
|
table: "States",
|
||||||
|
column: "CountryId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Cities",
|
||||||
|
schema: "GMS");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "States",
|
||||||
|
schema: "GMS");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Countries",
|
||||||
|
schema: "GMS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+282
@@ -1014,6 +1014,256 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("FactorDetails", "CMS");
|
b.ToTable("FactorDetails", "CMS");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", 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<long>("ExternalId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModified")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("LastModifiedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Latitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Longitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Native")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<long>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StateId")
|
||||||
|
.HasDatabaseName("IX_Cities_StateId");
|
||||||
|
|
||||||
|
b.ToTable("Cities", "GMS");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Capital")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("CurrencyName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("CurrencySymbol")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("Emoji")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("EmojiU")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<long>("ExternalId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Iso2")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2)
|
||||||
|
.HasColumnType("nvarchar(2)");
|
||||||
|
|
||||||
|
b.Property<string>("Iso3")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(3)
|
||||||
|
.HasColumnType("nvarchar(3)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModified")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("LastModifiedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Latitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Longitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Native")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("NumericCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("Region")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Subregion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Tld")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Countries", "GMS");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("CountryId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<long>("ExternalId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModified")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("LastModifiedBy")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Latitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Longitude")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Native")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)")
|
||||||
|
.HasDefaultValue("");
|
||||||
|
|
||||||
|
b.Property<string>("StateCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CountryId")
|
||||||
|
.HasDatabaseName("IX_States_CountryId");
|
||||||
|
|
||||||
|
b.ToTable("States", "GMS");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b =>
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@@ -2789,6 +3039,28 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Product");
|
b.Navigation("Product");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State")
|
||||||
|
.WithMany("Cities")
|
||||||
|
.HasForeignKey("StateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("State");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country")
|
||||||
|
.WithMany("States")
|
||||||
|
.HasForeignKey("CountryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Country");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b =>
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership")
|
b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership")
|
||||||
@@ -3149,6 +3421,16 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("ShoppingCarts");
|
b.Navigation("ShoppingCarts");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("States");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Cities");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("UserOrders");
|
b.Navigation("UserOrders");
|
||||||
|
|||||||
@@ -55,6 +55,8 @@
|
|||||||
<Protobuf Include="Protos\discountcategory.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
<Protobuf Include="Protos\discountcategory.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||||
<Protobuf Include="Protos\discountshoppingcart.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
<Protobuf Include="Protos\discountshoppingcart.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||||
<Protobuf Include="Protos\discountorder.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
<Protobuf Include="Protos\discountorder.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||||
|
<!-- Geography System (GMS) -->
|
||||||
|
<Protobuf Include="Protos\city.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PushToFoursatNuget" AfterTargets="Pack">
|
<Target Name="PushToFoursatNuget" AfterTargets="Pack">
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package city;
|
||||||
|
|
||||||
|
import "public_messages.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
|
||||||
|
option csharp_namespace = "CMSMicroservice.Protobuf.Protos.City";
|
||||||
|
|
||||||
|
service CityContract
|
||||||
|
{
|
||||||
|
rpc GetAllCitiesByFilter(GetAllCitiesByFilterRequest) returns (GetAllCitiesByFilterResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/GetAllCitiesByFilter"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllCitiesByFilterRequest
|
||||||
|
{
|
||||||
|
messages.PaginationState pagination_state = 1;
|
||||||
|
google.protobuf.StringValue sort_by = 2;
|
||||||
|
GetAllCitiesByFilterFilter filter = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllCitiesByFilterFilter
|
||||||
|
{
|
||||||
|
google.protobuf.Int64Value id = 1;
|
||||||
|
google.protobuf.StringValue name = 2;
|
||||||
|
google.protobuf.StringValue native = 3;
|
||||||
|
google.protobuf.Int64Value state_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllCitiesByFilterResponse
|
||||||
|
{
|
||||||
|
messages.MetaData meta_data = 1;
|
||||||
|
repeated GetAllCitiesByFilterResponseModel models = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllCitiesByFilterResponseModel
|
||||||
|
{
|
||||||
|
int64 id = 1;
|
||||||
|
int64 external_id = 2;
|
||||||
|
string name = 3;
|
||||||
|
string native = 4;
|
||||||
|
string latitude = 5;
|
||||||
|
string longitude = 6;
|
||||||
|
int64 state_id = 7;
|
||||||
|
string state_name = 8;
|
||||||
|
string state_native = 9;
|
||||||
|
}
|
||||||
@@ -63,6 +63,14 @@ service ClubMembershipContract
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// امضای قرارداد باشگاه مشتریان
|
||||||
|
rpc AcceptClubMembershipContract(AcceptClubMembershipContractRequest) returns (AcceptClubMembershipContractResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/ClubMembership/AcceptContract"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate Command
|
// Activate Command
|
||||||
@@ -258,3 +266,19 @@ message ToggleUserClubFeatureResponse
|
|||||||
google.protobuf.Int64Value user_club_feature_id = 3;
|
google.protobuf.Int64Value user_club_feature_id = 3;
|
||||||
google.protobuf.BoolValue is_active = 4;
|
google.protobuf.BoolValue is_active = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcceptClubMembershipContract Command - امضای قرارداد باشگاه مشتریان
|
||||||
|
message AcceptClubMembershipContractRequest
|
||||||
|
{
|
||||||
|
int64 user_id = 1;
|
||||||
|
string otp_code = 2;
|
||||||
|
string sign_guid = 3;
|
||||||
|
string contract_html = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AcceptClubMembershipContractResponse
|
||||||
|
{
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
int64 contract_id = 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ service UserContract
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/RefreshToken"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
message CreateNewUserRequest
|
message CreateNewUserRequest
|
||||||
{
|
{
|
||||||
@@ -191,3 +197,13 @@ message SetPasswordForUserRequest
|
|||||||
string new_password = 3;
|
string new_password = 3;
|
||||||
string confirm_password = 4;
|
string confirm_password = 4;
|
||||||
}
|
}
|
||||||
|
message RefreshTokenRequest
|
||||||
|
{
|
||||||
|
string current_token = 1;
|
||||||
|
}
|
||||||
|
message RefreshTokenResponse
|
||||||
|
{
|
||||||
|
string token = 1;
|
||||||
|
bool success = 2;
|
||||||
|
string message = 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="9.0.2" />
|
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="9.0.2" />
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||||
|
using Mapster;
|
||||||
|
using ProtoCity = CMSMicroservice.Protobuf.Protos.City;
|
||||||
|
using AppCity = CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||||
|
|
||||||
|
public class CityProfile : IRegister
|
||||||
|
{
|
||||||
|
void IRegister.Register(TypeAdapterConfig config)
|
||||||
|
{
|
||||||
|
// Request: Proto → Application
|
||||||
|
config.NewConfig<ProtoCity.GetAllCitiesByFilterRequest, AppCity.GetAllCitiesByFilterQuery>()
|
||||||
|
.Map(dest => dest.PaginationState, src => src.PaginationState)
|
||||||
|
.Map(dest => dest.SortBy, src => src.SortBy)
|
||||||
|
.Map(dest => dest.Filter, src => src.Filter);
|
||||||
|
|
||||||
|
config.NewConfig<ProtoCity.GetAllCitiesByFilterFilter, AppCity.GetAllCitiesByFilterFilter>()
|
||||||
|
.Map(dest => dest.Id, src => src.Id)
|
||||||
|
.Map(dest => dest.Name, src => src.Name)
|
||||||
|
.Map(dest => dest.Native, src => src.Native)
|
||||||
|
.Map(dest => dest.StateId, src => src.StateId);
|
||||||
|
|
||||||
|
// Response: Application → Proto
|
||||||
|
config.NewConfig<AppCity.GetAllCitiesByFilterResponseDto, ProtoCity.GetAllCitiesByFilterResponse>()
|
||||||
|
.Map(dest => dest.MetaData, src => src.MetaData)
|
||||||
|
.Map(dest => dest.Models, src => src.Models);
|
||||||
|
|
||||||
|
config.NewConfig<AppCity.GetAllCitiesByFilterResponseModel, ProtoCity.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.StateName)
|
||||||
|
.Map(dest => dest.StateNative, src => src.StateNative);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Application.ClubFeatureCQ.Commands.ToggleUserClubFeature;
|
using CMSMicroservice.Application.ClubFeatureCQ.Commands.ToggleUserClubFeature;
|
||||||
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||||
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||||
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using System;
|
using System;
|
||||||
@@ -43,5 +44,18 @@ public class ClubFeatureProfile : IRegister
|
|||||||
.Map(dest => dest.Message, src => src.Message)
|
.Map(dest => dest.Message, src => src.Message)
|
||||||
.Map(dest => dest.UserClubFeatureId, src => src.UserClubFeatureId.HasValue ? (long?)src.UserClubFeatureId.Value : null)
|
.Map(dest => dest.UserClubFeatureId, src => src.UserClubFeatureId.HasValue ? (long?)src.UserClubFeatureId.Value : null)
|
||||||
.Map(dest => dest.IsActive, src => src.IsActive.HasValue ? (bool?)src.IsActive.Value : null);
|
.Map(dest => dest.IsActive, src => src.IsActive.HasValue ? (bool?)src.IsActive.Value : null);
|
||||||
|
|
||||||
|
// AcceptClubMembershipContractRequest → AcceptClubMembershipContractCommand
|
||||||
|
config.NewConfig<AcceptClubMembershipContractRequest, AcceptClubMembershipContractCommand>()
|
||||||
|
.Map(dest => dest.UserId, src => src.UserId)
|
||||||
|
.Map(dest => dest.OtpCode, src => src.OtpCode)
|
||||||
|
.Map(dest => dest.SignGuid, src => src.SignGuid)
|
||||||
|
.Map(dest => dest.ContractHtml, src => src.ContractHtml);
|
||||||
|
|
||||||
|
// AcceptClubMembershipContractResponseDto → AcceptClubMembershipContractResponse
|
||||||
|
config.NewConfig<AcceptClubMembershipContractResponseDto, AcceptClubMembershipContractResponse>()
|
||||||
|
.Map(dest => dest.Success, src => src.Success)
|
||||||
|
.Map(dest => dest.Message, src => src.Message)
|
||||||
|
.Map(dest => dest.ContractId, src => src.ContractId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.WebApi.Hubs;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.WebApi.Common.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for sending token-related notifications via SignalR
|
||||||
|
/// </summary>
|
||||||
|
public class TokenNotificationService : ITokenNotificationService
|
||||||
|
{
|
||||||
|
private readonly IHubContext<TokenNotificationHub> _hubContext;
|
||||||
|
private readonly ILogger<TokenNotificationService> _logger;
|
||||||
|
|
||||||
|
public TokenNotificationService(
|
||||||
|
IHubContext<TokenNotificationHub> hubContext,
|
||||||
|
ILogger<TokenNotificationService> logger)
|
||||||
|
{
|
||||||
|
_hubContext = hubContext;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task NotifyTokenRevokedAsync(long userId, string reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Notifying token revoked for user {UserId}. Reason: {Reason}", userId, reason);
|
||||||
|
|
||||||
|
await _hubContext.Clients.Group($"user_{userId}")
|
||||||
|
.SendAsync("TokenRevoked", new TokenRevokedNotification
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Reason = reason,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInformation("Token revoked notification sent for user {UserId}", userId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send token revoked notification for user {UserId}", userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task NotifyForceRefreshAsync(long userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Notifying force refresh for user {UserId}", userId);
|
||||||
|
|
||||||
|
await _hubContext.Clients.Group($"user_{userId}")
|
||||||
|
.SendAsync("ForceRefreshToken", new ForceRefreshNotification
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInformation("Force refresh notification sent for user {UserId}", userId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send force refresh notification for user {UserId}", userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task BroadcastMessageAsync(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Broadcasting message to all clients: {Message}", message);
|
||||||
|
|
||||||
|
await _hubContext.Clients.All.SendAsync("BroadcastMessage", new BroadcastNotification
|
||||||
|
{
|
||||||
|
Message = message,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to broadcast message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification payload for token revoked event
|
||||||
|
/// </summary>
|
||||||
|
public class TokenRevokedNotification
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification payload for force refresh event
|
||||||
|
/// </summary>
|
||||||
|
public class ForceRefreshNotification
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notification payload for broadcast message
|
||||||
|
/// </summary>
|
||||||
|
public class BroadcastNotification
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Infrastructure.Persistence;
|
using CMSMicroservice.Infrastructure.Persistence;
|
||||||
using CMSMicroservice.WebApi.Common.Services;
|
using CMSMicroservice.WebApi.Common.Services;
|
||||||
|
using CMSMicroservice.WebApi.Hubs;
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using CMSMicroservice.WebApi.Services;
|
using CMSMicroservice.WebApi.Services;
|
||||||
@@ -22,6 +23,10 @@ public static class ConfigureServices
|
|||||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||||
services.AddScoped<IDispatchRequestToCQRS, DispatchRequestToCQRS>();
|
services.AddScoped<IDispatchRequestToCQRS, DispatchRequestToCQRS>();
|
||||||
|
|
||||||
|
// Add SignalR services
|
||||||
|
services.AddSignalR();
|
||||||
|
services.AddScoped<ITokenNotificationService, TokenNotificationService>();
|
||||||
|
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
services.AddHealthChecks()
|
services.AddHealthChecks()
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.WebApi.Hubs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SignalR Hub for broadcasting token-related notifications to connected BFF clients.
|
||||||
|
/// This hub is used internally between CMS and BFF services.
|
||||||
|
/// </summary>
|
||||||
|
public class TokenNotificationHub : Hub
|
||||||
|
{
|
||||||
|
private readonly ILogger<TokenNotificationHub> _logger;
|
||||||
|
|
||||||
|
public TokenNotificationHub(ILogger<TokenNotificationHub> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Client connected to TokenNotificationHub: {ConnectionId}", Context.ConnectionId);
|
||||||
|
await base.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Client disconnected from TokenNotificationHub: {ConnectionId}, Exception: {Exception}",
|
||||||
|
Context.ConnectionId, exception?.Message);
|
||||||
|
await base.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to notifications for a specific user
|
||||||
|
/// </summary>
|
||||||
|
public async Task SubscribeToUser(long userId)
|
||||||
|
{
|
||||||
|
await Groups.AddToGroupAsync(Context.ConnectionId, $"user_{userId}");
|
||||||
|
_logger.LogInformation("Client {ConnectionId} subscribed to user_{UserId}", Context.ConnectionId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribe from notifications for a specific user
|
||||||
|
/// </summary>
|
||||||
|
public async Task UnsubscribeFromUser(long userId)
|
||||||
|
{
|
||||||
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user_{userId}");
|
||||||
|
_logger.LogInformation("Client {ConnectionId} unsubscribed from user_{UserId}", Context.ConnectionId, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Infrastructure.Persistence;
|
using CMSMicroservice.Infrastructure.Persistence;
|
||||||
using CMSMicroservice.Infrastructure.Data.Seeding;
|
using CMSMicroservice.Infrastructure.Data.Seeding;
|
||||||
|
using CMSMicroservice.WebApi.Hubs;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@@ -169,6 +170,10 @@ app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthC
|
|||||||
});
|
});
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the HTTP request pipeline.
|
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the HTTP request pipeline.
|
||||||
|
|
||||||
|
// Map SignalR Hub for token notifications
|
||||||
|
app.MapHub<TokenNotificationHub>("/hubs/token-notification");
|
||||||
|
|
||||||
app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints =>
|
app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints =>
|
||||||
{
|
{
|
||||||
// endpoints.MapGrpcService<ExampleService>();
|
// endpoints.MapGrpcService<ExampleService>();
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using CMSMicroservice.Protobuf.Protos.City;
|
||||||
|
using CMSMicroservice.WebApi.Common.Services;
|
||||||
|
using CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
|
public class CityService : CityContract.CityContractBase
|
||||||
|
{
|
||||||
|
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||||
|
|
||||||
|
public CityService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
||||||
|
{
|
||||||
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetAllCitiesByFilterResponse> GetAllCitiesByFilter(
|
||||||
|
GetAllCitiesByFilterRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<
|
||||||
|
GetAllCitiesByFilterRequest,
|
||||||
|
GetAllCitiesByFilterQuery,
|
||||||
|
GetAllCitiesByFilterResponse>(request, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using CMSMicroservice.WebApi.Common.Services;
|
|||||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.DeactivateClubMembership;
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.DeactivateClubMembership;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AssignClubFeature;
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AssignClubFeature;
|
||||||
|
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
||||||
@@ -65,4 +66,9 @@ public class ClubMembershipService : ClubMembershipContract.ClubMembershipContra
|
|||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<ToggleUserClubFeatureRequest, ToggleUserClubFeatureCommand, Protobuf.Protos.ClubMembership.ToggleUserClubFeatureResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<ToggleUserClubFeatureRequest, ToggleUserClubFeatureCommand, Protobuf.Protos.ClubMembership.ToggleUserClubFeatureResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<AcceptClubMembershipContractResponse> AcceptClubMembershipContract(AcceptClubMembershipContractRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<AcceptClubMembershipContractRequest, AcceptClubMembershipContractCommand, AcceptClubMembershipContractResponse>(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using CMSMicroservice.Application.UserCQ.Queries.GetAllUserByFilter;
|
|||||||
using CMSMicroservice.Application.UserCQ.Queries.GetJwtToken;
|
using CMSMicroservice.Application.UserCQ.Queries.GetJwtToken;
|
||||||
using CMSMicroservice.Application.UserCQ.Queries.AdminGetJwtToken;
|
using CMSMicroservice.Application.UserCQ.Queries.AdminGetJwtToken;
|
||||||
using CMSMicroservice.Application.UserCQ.Commands.SetPasswordForUser;
|
using CMSMicroservice.Application.UserCQ.Commands.SetPasswordForUser;
|
||||||
|
using CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
public class UserService : UserContract.UserContractBase
|
public class UserService : UserContract.UserContractBase
|
||||||
{
|
{
|
||||||
@@ -49,4 +50,8 @@ public class UserService : UserContract.UserContractBase
|
|||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<SetPasswordForUserRequest, SetPasswordForUserCommand>(request, context);
|
return await _dispatchRequestToCQRS.Handle<SetPasswordForUserRequest, SetPasswordForUserCommand>(request, context);
|
||||||
}
|
}
|
||||||
|
public override async Task<RefreshTokenResponse> RefreshToken(RefreshTokenRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<RefreshTokenRequest, RefreshTokenCommand, RefreshTokenResponse>(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user