diff --git a/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommand.cs b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommand.cs new file mode 100644 index 0000000..1b31646 --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommand.cs @@ -0,0 +1,11 @@ +namespace FrontOffice.BFF.Application.UserCQ.Commands.SetPasswordForUser; +public record SetPasswordForUserCommand : IRequest +{ + //کلمه عبور فعلی + public string? CurrentPassword { get; init; } + //کلمه عبور + public string NewPassword { get; init; } + //تایید کلمه عبور + public string ConfirmPassword { get; init; } + +} \ No newline at end of file diff --git a/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandHandler.cs b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandHandler.cs new file mode 100644 index 0000000..8e9e30c --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandHandler.cs @@ -0,0 +1,16 @@ +namespace FrontOffice.BFF.Application.UserCQ.Commands.SetPasswordForUser; +public class SetPasswordForUserCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public SetPasswordForUserCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(SetPasswordForUserCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandValidator.cs b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandValidator.cs new file mode 100644 index 0000000..547365c --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Commands/SetPasswordForUser/SetPasswordForUserCommandValidator.cs @@ -0,0 +1,18 @@ +namespace FrontOffice.BFF.Application.UserCQ.Commands.SetPasswordForUser; +public class SetPasswordForUserCommandValidator : AbstractValidator +{ + public SetPasswordForUserCommandValidator() + { + RuleFor(model => model.NewPassword) + .NotEmpty(); + RuleFor(model => model.ConfirmPassword) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((SetPasswordForUserCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQuery.cs b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQuery.cs new file mode 100644 index 0000000..c5a8b56 --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQuery.cs @@ -0,0 +1,9 @@ +namespace FrontOffice.BFF.Application.UserCQ.Queries.AdminGetJwtToken; +public record AdminGetJwtTokenQuery : IRequest +{ + //نام کاربری + public string Username { get; init; } + //کلمه عبور + public string Password { get; init; } + +} \ No newline at end of file diff --git a/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryHandler.cs b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryHandler.cs new file mode 100644 index 0000000..c62ca02 --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryHandler.cs @@ -0,0 +1,16 @@ +namespace FrontOffice.BFF.Application.UserCQ.Queries.AdminGetJwtToken; +public class AdminGetJwtTokenQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public AdminGetJwtTokenQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(AdminGetJwtTokenQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new AdminGetJwtTokenResponseDto(); + } +} diff --git a/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryValidator.cs b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryValidator.cs new file mode 100644 index 0000000..316cbb3 --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenQueryValidator.cs @@ -0,0 +1,18 @@ +namespace FrontOffice.BFF.Application.UserCQ.Queries.AdminGetJwtToken; +public class AdminGetJwtTokenQueryValidator : AbstractValidator +{ + public AdminGetJwtTokenQueryValidator() + { + RuleFor(model => model.Username) + .NotEmpty(); + RuleFor(model => model.Password) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((AdminGetJwtTokenQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenResponseDto.cs b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenResponseDto.cs new file mode 100644 index 0000000..a14e488 --- /dev/null +++ b/src/FrontOffice.BFF.Application/UserCQ/Queries/AdminGetJwtToken/AdminGetJwtTokenResponseDto.cs @@ -0,0 +1,7 @@ +namespace FrontOffice.BFF.Application.UserCQ.Queries.AdminGetJwtToken; +public class AdminGetJwtTokenResponseDto +{ + //توکن + public string Token { get; set; } + +} \ No newline at end of file diff --git a/src/FrontOffice.BFF.WebApi/Common/Behaviours/LoggingBehaviour.cs b/src/FrontOffice.BFF.WebApi/Common/Behaviours/LoggingBehaviour.cs new file mode 100644 index 0000000..7f842fd --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Behaviours/LoggingBehaviour.cs @@ -0,0 +1,38 @@ +using Grpc.Core.Interceptors; +using Microsoft.Extensions.Logging; +using FrontOffice.BFF.Application.Common.Interfaces; + +namespace FrontOffice.BFF.WebApi.Common.Behaviours; + +public class LoggingBehaviour : Interceptor +{ + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + public LoggingBehaviour(ILogger logger, ICurrentUserService currentUserService) + { + _logger = logger; + _currentUserService = currentUserService; + } + + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + _logger.LogInformation("gRPC Starting receiving call. Type/Method: {Type} / {Method} Request: {Name} {@UserId} {@Request}", + MethodType.Unary, context.Method , requestName, userId, request); + + try + { + return await continuation(request, context); + } + catch (Exception ex) + { + _logger.LogError(ex, "gRPC Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + + throw; + } + } +} diff --git a/src/FrontOffice.BFF.WebApi/Common/Behaviours/PerformanceBehaviour.cs b/src/FrontOffice.BFF.WebApi/Common/Behaviours/PerformanceBehaviour.cs new file mode 100644 index 0000000..f34a289 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Behaviours/PerformanceBehaviour.cs @@ -0,0 +1,44 @@ +using Grpc.Core.Interceptors; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using FrontOffice.BFF.Application.Common.Interfaces; + +namespace FrontOffice.BFF.WebApi.Common.Behaviours; + +public class PerformanceBehaviour : Interceptor +{ + private readonly Stopwatch _timer; + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) + { + _timer = new Stopwatch(); + _logger = logger; + _currentUserService = currentUserService; + } + + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + _timer.Start(); + + var response = await continuation(request, context); + + _timer.Stop(); + + var elapsedMilliseconds = _timer.ElapsedMilliseconds; + + if (elapsedMilliseconds > 500) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + + _logger.LogWarning("gRPC Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}", + requestName, elapsedMilliseconds, userId, request); + } + return response; + } +} + diff --git a/src/FrontOffice.BFF.WebApi/Services/UserService.cs b/src/FrontOffice.BFF.WebApi/Services/UserService.cs index 132c948..741e0a1 100644 --- a/src/FrontOffice.BFF.WebApi/Services/UserService.cs +++ b/src/FrontOffice.BFF.WebApi/Services/UserService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.User.Protobuf.Protos.User; +using FrontOffice.BFF.Protobuf.Protos.User; using FrontOffice.BFF.WebApi.Common.Services; using FrontOffice.BFF.Application.UserCQ.Commands.UpdateUser; using FrontOffice.BFF.Application.UserCQ.Commands.DeleteUser; @@ -6,6 +6,8 @@ using FrontOffice.BFF.Application.UserCQ.Queries.GetUser; using FrontOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; using FrontOffice.BFF.Application.UserCQ.Commands.CreateNewOtpToken; using FrontOffice.BFF.Application.UserCQ.Commands.VerifyOtpToken; +using FrontOffice.BFF.Application.UserCQ.Queries.AdminGetJwtToken; +using FrontOffice.BFF.Application.UserCQ.Commands.SetPasswordForUser; namespace FrontOffice.BFF.WebApi.Services; public class UserService : UserContract.UserContractBase { @@ -39,4 +41,12 @@ public class UserService : UserContract.UserContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + public override async Task AdminGetJwtToken(AdminGetJwtTokenRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task SetPasswordForUser(SetPasswordForUserRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } } diff --git a/src/Protobufs/FrontOffice.BFF.User.Protobuf/Protos/user.proto b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Protos/user.proto index e1ddf6f..b4c270f 100644 --- a/src/Protobufs/FrontOffice.BFF.User.Protobuf/Protos/user.proto +++ b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Protos/user.proto @@ -48,6 +48,18 @@ service UserContract body: "*" }; }; + rpc AdminGetJwtToken(AdminGetJwtTokenRequest) returns (AdminGetJwtTokenResponse){ + option (google.api.http) = { + get: "/AdminGetJwtToken" + + }; + }; + rpc SetPasswordForUser(SetPasswordForUserRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/SetPasswordForUser" + body: "*" + }; + }; } message UpdateUserRequest { @@ -161,31 +173,47 @@ message VerifyOtpTokenResponse int32 remaining_attempts = 4; int32 remaining_seconds = 5; } - -message PaginationState +message AdminGetJwtTokenRequest { - int32 page_number = 1; - - int32 page_size = 2; + string username = 1; + string password = 2; } -message MetaData +message AdminGetJwtTokenResponse { - int64 current_page = 1; - - int64 total_page = 2; - - int64 page_size = 3; - - int64 total_count = 4; - - bool has_previous = 5; - - bool has_next = 6; + string token = 1; } -message DecimalValue +message SetPasswordForUserRequest { - - int64 units = 1; - - sfixed32 nanos = 2; + google.protobuf.StringValue current_password = 1; + string new_password = 2; + string confirm_password = 3; } + + message PaginationState + { + int32 page_number = 1; + + int32 page_size = 2; + } + message MetaData + { + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; + } + message DecimalValue + { + + int64 units = 1; + + sfixed32 nanos = 2; + } + \ No newline at end of file diff --git a/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/AdminGetJwtTokenRequestValidator.cs b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/AdminGetJwtTokenRequestValidator.cs new file mode 100644 index 0000000..2ea9a05 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/AdminGetJwtTokenRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using FrontOfficeMicroservice.Protobuf.Protos.User; +namespace FrontOfficeMicroservice.Protobuf.Validator.User; + +public class AdminGetJwtTokenRequestValidator : AbstractValidator +{ + public AdminGetJwtTokenRequestValidator() + { + RuleFor(model => model.Username) + .NotEmpty(); + RuleFor(model => model.Password) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((AdminGetJwtTokenRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/SetPasswordForUserRequestValidator.cs b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/SetPasswordForUserRequestValidator.cs new file mode 100644 index 0000000..5c86518 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.User.Protobuf/Validator/SetPasswordForUserRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using FrontOfficeMicroservice.Protobuf.Protos.User; +namespace FrontOfficeMicroservice.Protobuf.Validator.User; + +public class SetPasswordForUserRequestValidator : AbstractValidator +{ + public SetPasswordForUserRequestValidator() + { + RuleFor(model => model.NewPassword) + .NotEmpty(); + RuleFor(model => model.ConfirmPassword) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((SetPasswordForUserRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +}