feat: add geography entities with countries, states and cities
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.0" />
|
||||
</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.Queries.GetUserClubFeatures;
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using System;
|
||||
@@ -43,5 +44,18 @@ public class ClubFeatureProfile : IRegister
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.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);
|
||||
|
||||
// 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.Infrastructure.Persistence;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.WebApi.Hubs;
|
||||
using MapsterMapper;
|
||||
using System.Reflection;
|
||||
using CMSMicroservice.WebApi.Services;
|
||||
@@ -21,6 +22,10 @@ public static class ConfigureServices
|
||||
|
||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
services.AddScoped<IDispatchRequestToCQRS, DispatchRequestToCQRS>();
|
||||
|
||||
// Add SignalR services
|
||||
services.AddSignalR();
|
||||
services.AddScoped<ITokenNotificationService, TokenNotificationService>();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
|
||||
@@ -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.Data.Seeding;
|
||||
using CMSMicroservice.WebApi.Hubs;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -169,6 +170,10 @@ app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthC
|
||||
});
|
||||
app.MapControllers();
|
||||
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 =>
|
||||
{
|
||||
// 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.DeactivateClubMembership;
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AssignClubFeature;
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||
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);
|
||||
}
|
||||
|
||||
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.AdminGetJwtToken;
|
||||
using CMSMicroservice.Application.UserCQ.Commands.SetPasswordForUser;
|
||||
using CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class UserService : UserContract.UserContractBase
|
||||
{
|
||||
@@ -49,4 +50,8 @@ public class UserService : UserContract.UserContractBase
|
||||
{
|
||||
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