From 35545aa9b3135cc7252d8adf9598901a8521d191 Mon Sep 17 00:00:00 2001 From: mohammad parsa karimi Date: Wed, 4 Jun 2025 01:02:33 +0330 Subject: [PATCH 1/3] AddComment Controller --- DigiLearn.sln | 6 + .../Controllers/BlogController.cs | 99 +++++++++++ .../Controllers/CommentController.cs | 53 ++++++ .../Controllers/TicketController.cs | 53 ++++++ .../Controllers/TransactionController.cs | 127 +++++++++++++ .../Controllers/UserController.cs | 60 +++++++ .../Controllers/WeatherForecastController.cs | 33 ++++ .../DigiLearn.WebApi/DigiLearn.WebApi.csproj | 28 +++ .../DigiLearn.WebApi/DigiLearn.WebApi.http | 6 + .../Infrastructure/ApiController.cs | 103 +++++++++++ .../Infrastructure/ApiResult.cs | 28 +++ .../Infrastructure/ClaimUtils.cs | 45 +++++ .../Infrastructure/Common.AspNetCore.csproj | 19 ++ .../JwtUtils/JwtAuthenticationConfig.cs | 33 ++++ .../JwtUtils/JwtTokenBuilder.cs | 34 ++++ .../Middlewares/ApiCustomExceptionHandler.cs | 105 +++++++++++ .../Infrastructure/ModelStateUtil.cs | 30 ++++ .../RegisterDependencyServices.cs | 15 ++ .../DigiLearn.WebApi/Models/LoginResultDto.cs | 7 + .../Models/UserLoginCommandViewModel.cs | 9 + src/EndPoints/DigiLearn.WebApi/Program.cs | 167 ++++++++++++++++++ .../Properties/launchSettings.json | 41 +++++ .../DigiLearn.WebApi/WeatherForecast.cs | 13 ++ .../appsettings.Development.json | 8 + .../DigiLearn.WebApi/appsettings.json | 14 ++ 25 files changed, 1136 insertions(+) create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/WeatherForecastController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj create mode 100644 src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.http create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiResult.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/Common.AspNetCore.csproj create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtAuthenticationConfig.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtTokenBuilder.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/Middlewares/ApiCustomExceptionHandler.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/ModelStateUtil.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/RegisterDependencyServices.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Program.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Properties/launchSettings.json create mode 100644 src/EndPoints/DigiLearn.WebApi/WeatherForecast.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/appsettings.Development.json create mode 100644 src/EndPoints/DigiLearn.WebApi/appsettings.json diff --git a/DigiLearn.sln b/DigiLearn.sln index edbc4ed..3163dad 100644 --- a/DigiLearn.sln +++ b/DigiLearn.sln @@ -65,6 +65,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Transaction", "Transaction" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionModule", "src\Modules\Transaction\TransactionModule.csproj", "{7855A4BE-AF70-4196-BE58-661EF9BB7AF9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigiLearn.WebApi", "src\EndPoints\DigiLearn.WebApi\DigiLearn.WebApi.csproj", "{71B252BB-9EBA-4853-BDE3-6A9FC3CD5017}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -143,6 +145,10 @@ Global {7855A4BE-AF70-4196-BE58-661EF9BB7AF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {7855A4BE-AF70-4196-BE58-661EF9BB7AF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7855A4BE-AF70-4196-BE58-661EF9BB7AF9}.Release|Any CPU.Build.0 = Release|Any CPU + {71B252BB-9EBA-4853-BDE3-6A9FC3CD5017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71B252BB-9EBA-4853-BDE3-6A9FC3CD5017}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71B252BB-9EBA-4853-BDE3-6A9FC3CD5017}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71B252BB-9EBA-4853-BDE3-6A9FC3CD5017}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs new file mode 100644 index 0000000..304c4ae --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs @@ -0,0 +1,99 @@ +using BlogModule.Services; +using BlogModule.Services.DTOs.Command; +using BlogModule.Services.DTOs.Query; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class BlogController : ApiController + { + private readonly IBlogService _service; + + public BlogController(IBlogService service) + { + _service = service; + } + + #region Blog + [Authorize] + [HttpPost("CreatePost")] + public async Task CreatePost([FromForm] CreatePostCommand command) + { + return CommandResult(await _service.CreatePost(command)); + } + + [HttpPatch("EditPost")] + public async Task EditPost([FromForm] EditPostCommand command) + { + return CommandResult(await _service.EditPost(command)); + } + + /// + /// این متد کار حذف پست رو انجام میده + /// + /// + /// + /// نمونه ریکوئست => api/Blog/DeletePost?{id} + /// + /// + [HttpDelete("DeletePost")] + public async Task DeletePost(Guid postId) + { + return CommandResult(await _service.DeletePost(postId)); + } + + [HttpGet("GetPostById")] + public async Task> GetPostById(Guid postId) + { + return QueryResult(await _service.GetPostById(postId)); + } + [HttpGet("GetPostBySlug")] + public async Task> GetPostBySlug(string slug) + { + return QueryResult(await _service.GetPostBySlug(slug)); + } + [HttpGet("GetPostsByFilter")] + public async Task> GetPostsByFilter([FromQuery]BlogPostFilterParams filterParams) + { + return QueryResult(await _service.GetPostsByFilter(filterParams)); + } + #endregion + + //PostCategory + #region Category + [HttpPost("CreateCategory")] + public async Task CreateCategory + (CreateBlogCategoryCommand command) + { + return CommandResult(await _service.CreateCategory(command)); + } + [HttpPatch("EditCategory")] + public async Task EditCategory + (EditBlogCategoryCommand command) + { + return CommandResult(await _service.EditCategory(command)); + } + [HttpDelete("DeleteCategory")] + public async Task DeleteCategory + (Guid categoryId) + { + return CommandResult(await _service.DeleteCategory(categoryId)); + } + [HttpGet("GetCategoryById")] + public async Task> GetCategoryById(Guid categoryId) + { + return QueryResult(await _service.GetCategoryById(categoryId)); + } + [HttpGet("GetAllCategories")] + public async Task>> GetAllCategories() + { + return QueryResult(await _service.GetAllCategories()); + } + #endregion + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs new file mode 100644 index 0000000..36d99ad --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs @@ -0,0 +1,53 @@ +using BlogModule.Services.DTOs.Command; +using BlogModule.Services.DTOs.Query; +using CommentModule.Services; +using CommentModule.Services.DTOs; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CommentController : ApiController + { + private readonly ICommentService _service; + + public CommentController(ICommentService service) + { + _service = service; + } + + + [HttpPost("CreateComment")] + public async Task CreateComment(CreateCommentCommand command) + { + return CommandResult(await _service.CreateComment(command)); + } + + [HttpDelete("DeleteComment")] + public async Task DeleteComment(Guid commentId) + { + return CommandResult(await _service.DeleteComment(commentId)); + } + + [HttpGet("GetCommentById")] + public async Task> GetCommentById(Guid commentId) + { + return QueryResult(await _service.GetCommentById(commentId)); + } + [HttpGet("GetCommentByFilter")] + public async Task> GetCommentByFilter + ([FromQuery] CommentFilterParams filterParams) + { + return QueryResult(await _service.GetCommentByFilter(filterParams)); + } + [HttpGet("GetAllComments")] + public async Task> GetAllComments + ([FromQuery] CommentFilterParams filterParams) + { + return QueryResult(await _service.GetAllComments(filterParams)); + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs new file mode 100644 index 0000000..004770e --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs @@ -0,0 +1,53 @@ +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TicketModule.Core.DTOs.Tickets; +using TicketModule.Core.Services; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TicketController : ApiController + { + private readonly ITicketService _service; + + public TicketController(ITicketService service) + { + _service = service; + } + + [HttpPost("CreateTicket")] + public async Task> CreateTicket(CreateTicketCommand command) + { + var result = await _service.CreateTicket(command); + return CommandResult(result); + } + [HttpPost("SendTicketMessage")] + public async Task SendMessageInTicket(SendTicketMessageCommand command) + { + var result = await _service.SendMessageInTicket(command); + return CommandResult(result); + } + [HttpPost("CloseTicket")] + public async Task CloseTicket(Guid ticketId) + { + var result = await _service.CloseTicket(ticketId); + return CommandResult(result); + } + [HttpGet("GetTicket")] + public async Task> GetTicket(Guid ticketId) + { + var result = await _service.GetTicket(ticketId); + return QueryResult(result); + } + [HttpGet("GetTicketByFilter")] + public async Task> GetTicketsByFilter + ([FromQuery] TicketFilterParams filterParams) + { + var result = await _service.GetTicketsByFilter(filterParams); + return QueryResult(result); + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs new file mode 100644 index 0000000..782f3f4 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs @@ -0,0 +1,127 @@ +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TransactionModule.Domain; +using TransactionModule.Services; +using TransactionModule.Services.DTOs.Commands; +using TransactionModule.Services.DTOs.Queries; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TransactionController : ApiController + { + private readonly IUserTransactionService _service; + + public TransactionController(IUserTransactionService service) + { + _service = service; + } + #region Commands + [HttpPost("CreateTransaction")] + public async Task> CreateTransaction(CreateTransactionCommand command) + { + try + { + Guid transactionId = await _service.CreateTransaction(command); + return CommandResult(new Common.Application.OperationResult + { + Message = "عملیات با موفقیت انجام شد.", + Status = Common.Application.OperationResultStatus.Success, + Title = "عملیات موفق", + Data = transactionId + }); + + } + catch (Exception ex) + { + return CommandResult(new Common.Application.OperationResult + { + Message = "خطایی در عملیات رخ داده است!", + Status = Common.Application.OperationResultStatus.Error, + Title = "عملیات ناموفق" + }); + } + } + [HttpPost("PaymentSuccess")] + public async Task PaymentSuccess(TransactionPaymentSuccessCommand command) + { + try + { + await _service.PaymentSuccess(command); + return CommandResult(new Common.Application.OperationResult + { + Message = "عملیات با موفقیت انجام شد.", + Status = Common.Application.OperationResultStatus.Success, + Title = "عملیات موفق" + }); + + } + catch (Exception ex) + { + return CommandResult(new Common.Application.OperationResult + { + Message = "خطایی در عملیات رخ داده است!", + Status = Common.Application.OperationResultStatus.Error, + Title = "عملیات ناموفق" + }); + } + } + [HttpPost("PaymentError")] + public async Task PaymentError(TransactionPaymentErrorCommand command) + { + try + { + await _service.PaymentError(command); + return CommandResult(new Common.Application.OperationResult + { + Message = "عملیات با موفقیت انجام شد.", + Status = Common.Application.OperationResultStatus.Success, + Title = "عملیات موفق" + }); + + } + catch (Exception ex) + { + return CommandResult(new Common.Application.OperationResult + { + Message = "خطایی در عملیات رخ داده است!", + Status = Common.Application.OperationResultStatus.Error, + Title = "عملیات ناموفق" + }); + } + } + #endregion + + #region Queries + + + /// + /// نمونه ریکوئست => api/Transaction/GetTransactionById?{transactionId} + /// + [HttpGet("GetUserTransaction")] + public async Task> GetTransactionById(Guid transactionId) + { + return QueryResult(await _service.GetTransactionById(transactionId)); + } + + [HttpGet("GetTransactionsByFilter")] + public async Task> GetTransactionsByFilter + ([FromQuery] UserTransactionFilterParams queryParams) + { + return QueryResult(await _service.GetTransactionsByFilter(queryParams)); + } + + [HttpGet("GetCancelTransactionsCount")] + public async Task> GetCancelTransactionsCount + ([FromQuery] string startDate, string endDate, + TransactionFor transactionFor, TransactionStatus status) + { + return QueryResult(await _service.GetCancelTransactionsCount(startDate, + endDate, transactionFor, status)); + } + + #endregion + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs new file mode 100644 index 0000000..8301971 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs @@ -0,0 +1,60 @@ +using Common.Application; +using Common.Application.SecurityUtil; +using DigiLearn.WebApi.Infrastructure; +using DigiLearn.WebApi.Infrastructure.JwtUtils; +using DigiLearn.WebApi.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using UserModule.Core.Commands.Users.Register; +using UserModule.Core.Services; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class UserController : ApiController + { + private readonly IConfiguration _configuration; + private readonly IUserFacade _facade; + + public UserController(IUserFacade facade, IConfiguration configuration) + { + _facade = facade; + _configuration = configuration; + } + + #region Auth + + [HttpPost("RegisterUser")] + public async Task> Register([FromBody] RegisterUserCommand command) + { + return CommandResult(await _facade.RegisterUser(command)); + } + + [HttpPost("LoginUser")] + public async Task> LoginUser( + [FromBody] UserLoginCommandViewModel command) + { + var user = await _facade.GetUserByPhoneNumber(command.PhoneNumber); + if (user == null) + { + return CommandResult( + OperationResult.Error("کاربری با مشخصات وارد شده یافت نشد")); + } + if (Sha256Hasher.IsCompare(user.Password, command.Password) == false) + { + return CommandResult( + OperationResult.Error("کاربری با مشحصات وارد شده یافت نشد")); + } + + var token = JwtTokenBuilder.BuildToken(user, command.RememberMe, _configuration); + //JwtTokenBuilder + return CommandResult( + OperationResult.Success + (new LoginResultDto { Token = token })); + } + + #endregion + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/WeatherForecastController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..7b723cf --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj new file mode 100644 index 0000000..84d8085 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.http b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.http new file mode 100644 index 0000000..8c93fdd --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.http @@ -0,0 +1,6 @@ +@DigiLearn.WebApi_HostAddress = http://localhost:5017 + +GET {{DigiLearn.WebApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiController.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiController.cs new file mode 100644 index 0000000..8f3bb77 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiController.cs @@ -0,0 +1,103 @@ +using System.Net; +using Common.Application; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace DigiLearn.WebApi.Infrastructure; + +[Route("api/[controller]")] +[ApiController] +public class ApiController : ControllerBase +{ + protected ApiResult CommandResult(OperationResult result) + { + return new ApiResult() + { + IsSuccess = result.Status == OperationResultStatus.Success, + MetaData = new() + { + Message = result.Message, + AppStatusCode = result.Status.MapOperationStatus() + } + }; + } + protected ApiResult CommandResult(OperationResult result + , HttpStatusCode statusCode = HttpStatusCode.OK, string locationUrl = null) + { + bool isSuccess = result.Status == OperationResultStatus.Success; + if (isSuccess) + { + HttpContext.Response.StatusCode = (int)statusCode; + if (!string.IsNullOrWhiteSpace(locationUrl)) + { + HttpContext.Response.Headers.Add("location", locationUrl); + } + } + return new ApiResult() + { + IsSuccess = isSuccess, + Data = isSuccess ? result.Data : default, + MetaData = new() + { + Message = result.Message, + AppStatusCode = result.Status.MapOperationStatus() + } + }; + } + + protected ApiResult QueryResult(TData result) + { + return new ApiResult() + { + IsSuccess = true, + Data = result, + MetaData = new() + { + Message = "عملیات با موفقیت انجام شد", + AppStatusCode = AppStatusCode.Success + } + }; + } + protected string JoinErrors() + { + var errors = new Dictionary>(); + + if (!ModelState.IsValid) + { + if (ModelState.ErrorCount > 0) + { + for (int i = 0; i < ModelState.Values.Count(); i++) + { + var key = ModelState.Keys.ElementAt(i); + var value = ModelState.Values.ElementAt(i); + + if (value.ValidationState == ModelValidationState.Invalid) + { + errors.Add(key, value.Errors.Select(x => string.IsNullOrEmpty(x.ErrorMessage) ? x.Exception?.Message : x.ErrorMessage).ToList()); + } + } + } + } + var error = string.Join(" ", errors.Select(x => $"{string.Join(" - ", x.Value)}")); + return error; + } +} + +public static class EnumHelper +{ + public static AppStatusCode MapOperationStatus(this OperationResultStatus status) + { + switch (status) + { + case OperationResultStatus.Success: + return AppStatusCode.Success; + + case OperationResultStatus.NotFound: + return AppStatusCode.NotFound; + + case OperationResultStatus.Error: + return AppStatusCode.LogicError; + } + return AppStatusCode.LogicError; + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiResult.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiResult.cs new file mode 100644 index 0000000..c4074c5 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ApiResult.cs @@ -0,0 +1,28 @@ +namespace DigiLearn.WebApi.Infrastructure; + +public class ApiResult +{ + public bool IsSuccess { get; set; } + public MetaData MetaData { get; set; } +} +public class ApiResult +{ + public bool IsSuccess { get; set; } + public TData Data { get; set; } + public MetaData MetaData { get; set; } +} +public class MetaData +{ + public string Message { get; set; } + public AppStatusCode AppStatusCode { get; set; } +} + +public enum AppStatusCode +{ + Success = 1, + NotFound = 2, + BadRequest = 3, + LogicError = 4, + UnAuthorize = 5, + ServerError +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs new file mode 100644 index 0000000..c35daba --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs @@ -0,0 +1,45 @@ +using System.Security.Claims; +using System.Security.Principal; + +namespace DigiLearn.WebApi.Infrastructure; + +public static class ClaimUtils +{ + public static long GetUserId(this ClaimsPrincipal principal) + { + if (principal == null) + throw new ArgumentNullException(nameof(principal)); + + return Convert.ToInt64(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value); + } + public static string GetUserIdToString(this ClaimsPrincipal principal) + { + if (principal == null) + throw new ArgumentNullException(nameof(principal)); + + //return Convert.ToString(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value); + return Convert.ToString(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value)!; + } + public static string GetUserName(this ClaimsPrincipal principal) + { + if (principal == null) + throw new ArgumentNullException(nameof(principal)); + + return Convert.ToString(principal.FindFirst(ClaimTypes.Name)?.Value!); + + } + public static string GetPhoneNumber(this ClaimsPrincipal principal) + { + if (principal == null) + throw new ArgumentNullException(nameof(principal)); + + return principal.FindFirst(ClaimTypes.MobilePhone)?.Value; + } + public static string GetEmail(this IIdentity identity) + { + ClaimsIdentity claimsIdentity = identity as ClaimsIdentity; + Claim? claim = claimsIdentity?.FindFirst("Id"); + return claim?.Value ?? string.Empty; + } + +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/Common.AspNetCore.csproj b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Common.AspNetCore.csproj new file mode 100644 index 0000000..71f45bf --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Common.AspNetCore.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtAuthenticationConfig.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtAuthenticationConfig.cs new file mode 100644 index 0000000..fcae16d --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtAuthenticationConfig.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; + +namespace DigiLearn.WebApi.Infrastructure.JwtUtils +{ + public static class JwtAuthenticationConfig + { + public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration) + { + services.AddAuthentication(option => + { + option.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; + option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(option => + { + option.TokenValidationParameters = new TokenValidationParameters() + { + IssuerSigningKey = + new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtConfig:SignInKey"])), + ValidIssuer = configuration["JwtConfig:Issuer"], + ValidAudience = configuration["JwtConfig:Audience"], + ValidateLifetime = true, + ValidateIssuer = true, + ValidateIssuerSigningKey = true, + ValidateAudience = true + }; + option.SaveToken = true; + }); + } + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtTokenBuilder.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtTokenBuilder.cs new file mode 100644 index 0000000..1a24877 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/JwtUtils/JwtTokenBuilder.cs @@ -0,0 +1,34 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.IdentityModel.Tokens; +using UserModule.Core.Queries._DTOs; + +namespace DigiLearn.WebApi.Infrastructure.JwtUtils; + +public class JwtTokenBuilder +{ + public static string BuildToken(UserDto user, bool RememberMe, IConfiguration configuration) + { + var roles = user.Roles.Select(s => s.Title); + var claims = new List() + { + new Claim(ClaimTypes.MobilePhone,user.PhoneNumber), + new Claim(ClaimTypes.NameIdentifier,user.Id.ToString()), + new Claim(ClaimTypes.Role,string.Join("-",roles)) + }; + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtConfig:SignInKey"])); + var credential = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); + var expiration = + RememberMe == true ? DateTime.Now.AddDays(30) + : DateTime.Now.AddDays(1); + var token = new JwtSecurityToken( + issuer: configuration["JwtConfig:Issuer"], + audience: configuration["JwtConfig:Audience"], + claims: claims, + expires: expiration, + signingCredentials: credential); + + return new JwtSecurityTokenHandler().WriteToken(token); + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/Middlewares/ApiCustomExceptionHandler.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Middlewares/ApiCustomExceptionHandler.cs new file mode 100644 index 0000000..51ae5aa --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Middlewares/ApiCustomExceptionHandler.cs @@ -0,0 +1,105 @@ +using System.Net; +using Common.Application.Validation; +using Common.Domain.Exceptions; +using DigiLearn.WebApi.Infrastructure; +using Newtonsoft.Json; +using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; + +namespace DigiLearn.WebApi.Infrastructure.Middlewares; + +public static class ApiCustomExceptionHandlerMiddlewareExtensions +{ + public static IApplicationBuilder UseApiCustomExceptionHandler(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} + +public class ApiCustomExceptionHandlerMiddleware +{ + private readonly RequestDelegate _next; + private readonly IWebHostEnvironment _env; + private readonly ILogger _logger; + + public ApiCustomExceptionHandlerMiddleware(RequestDelegate next, + IWebHostEnvironment env, + ILogger logger) + { + _next = next; + _env = env; + _logger = logger; + } + + public async Task Invoke(HttpContext context) + { + string message = null; + HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError; + AppStatusCode apiStatusCode = AppStatusCode.ServerError; + + try + { + await _next(context); + } + catch (InvalidDomainDataException exception) + { + _logger.LogError(exception, exception.Message); + apiStatusCode = AppStatusCode.LogicError; + SetErrorMessage(exception); + await WriteToResponseAsync(); + } + catch (InvalidCommandException exception) + { + _logger.LogError(exception, exception.Message); + httpStatusCode = HttpStatusCode.BadRequest; + SetErrorMessage(exception); + await WriteToResponseAsync(); + } + catch (Exception exception) + { + _logger.LogError(exception, exception.Message); + + SetErrorMessage(exception); + await WriteToResponseAsync(); + } + + void SetErrorMessage(Exception exception) + { + message = exception.Message; + if (_env.IsDevelopment()) + { + var dic = new Dictionary + { + ["Exception"] = exception.Message, + ["StackTrace"] = exception.StackTrace, + }; + if (exception.InnerException != null) + { + dic.Add("InnerException.Exception", exception.InnerException.Message); + dic.Add("InnerException.StackTrace", exception.InnerException.StackTrace); + } + + message = JsonConvert.SerializeObject(dic); + } + } + async Task WriteToResponseAsync() + { + if (context.Response.HasStarted) + throw new InvalidOperationException("The response has already started, the http status code middleware will not be executed."); + + var result = new ApiResult() + { + IsSuccess = false, + MetaData = new() + { + AppStatusCode = apiStatusCode, + Message = message + } + }; + var json = JsonConvert.SerializeObject(result); + + context.Response.StatusCode = (int)httpStatusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(json); + } + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ModelStateUtil.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ModelStateUtil.cs new file mode 100644 index 0000000..13ac1d1 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ModelStateUtil.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace DigiLearn.WebApi.Infrastructure; + +public class ModelStateUtil +{ + public static string GetModelStateErrors(ModelStateDictionary modelState) + { + var errors = new Dictionary>(); + + if (!modelState.IsValid) + { + if (modelState.ErrorCount > 0) + { + for (int i = 0; i < modelState.Values.Count(); i++) + { + var key = modelState.Keys.ElementAt(i); + var value = modelState.Values.ElementAt(i); + + if (value.ValidationState == ModelValidationState.Invalid) + { + errors.Add(key, value.Errors.Select(x => string.IsNullOrEmpty(x.ErrorMessage) ? x.Exception?.Message : x.ErrorMessage).ToList()); + } + } + } + } + var error = string.Join(" - ", errors.Select(x => $"{string.Join(" - ", x.Value)}")); + return error; + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/RegisterDependencyServices.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/RegisterDependencyServices.cs new file mode 100644 index 0000000..abd0756 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/RegisterDependencyServices.cs @@ -0,0 +1,15 @@ +using Common.EventBus.Abstractions; +using Common.EventBus.RabbitMQ; + +namespace DigiLearn.WebApi.Infrastructure +{ + public static class RegisterDependencyServices + { + public static IServiceCollection RegisterWebDependencies(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs b/src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs new file mode 100644 index 0000000..bd1d7b7 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs @@ -0,0 +1,7 @@ +namespace DigiLearn.WebApi.Models +{ + public class LoginResultDto + { + public string? Token { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs new file mode 100644 index 0000000..7746e53 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs @@ -0,0 +1,9 @@ +namespace DigiLearn.WebApi.Models +{ + public class UserLoginCommandViewModel + { + public required string PhoneNumber { get; set; } + public required string Password { get; set; } + public bool RememberMe { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Program.cs b/src/EndPoints/DigiLearn.WebApi/Program.cs new file mode 100644 index 0000000..dcf8576 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Program.cs @@ -0,0 +1,167 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Mvc; +using BlogModule; +using CommentModule; +using CoreModule.Config; +using TicketModule; +using TransactionModule; +using UserModule.Core; +using Microsoft.OpenApi.Models; +using DigiLearn.WebApi.Infrastructure; +using Common.Application.FileUtil.Interfaces; +using Common.Application.FileUtil.Services; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Authorization; +using DigiLearn.WebApi.Infrastructure.Middlewares; +using DigiLearn.WebApi.Infrastructure.JwtUtils; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +//builder.Services.AddControllers() +// .ConfigureApiBehaviorOptions(option => +// { +// option.InvalidModelStateResponseFactory = (context => +// { +// var result = new ApiResult() +// { +// IsSuccess = false, +// MetaData = new() +// { +// AppStatusCode = AppStatusCode.BadRequest, +// Message = ModelStateUtil.GetModelStateErrors(context.ModelState) +// } +// }; +// return new BadRequestObjectResult(result); +// }); +// }); +builder.Services.AddControllers(options => // تغییرات در این بخش +{ + // افزودن فیلتر جهانی [Authorize] + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + options.Filters.Add(new AuthorizeFilter(policy)); +}) +.ConfigureApiBehaviorOptions(option => +{ + option.InvalidModelStateResponseFactory = (context) => + { + // کدهای قبلی شما + var result = new ApiResult() + { + IsSuccess = false, + MetaData = new() + { + AppStatusCode = AppStatusCode.BadRequest, + Message = ModelStateUtil.GetModelStateErrors(context.ModelState) + } + }; + return new BadRequestObjectResult(result); + }; +}); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(option => +{ + var jwtSecurityScheme = new OpenApiSecurityScheme + { + Scheme = "Bearer", + BearerFormat = "JWT", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Description = "Enter Token", + + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + + option.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); + option.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme + } + }, + Array.Empty() + } + }); + //option.AddSecurityRequirement(new OpenApiSecurityRequirement + //{ + // { jwtSecurityScheme, Array.Empty() } + //}); +// option.AddSecurityRequirement(new OpenApiSecurityRequirement +//{ +// { +// jwtSecurityScheme, +// new[] { JwtBearerDefaults.AuthenticationScheme } // ????? ???? ????? ????? +// } + +//}); +}); + + +builder.Services.AddTransient(); +builder.Services.AddTransient(); + + +builder.Services + .InitUserModule(builder.Configuration) + .InitTicketModule(builder.Configuration) + .InitCoreModule(builder.Configuration) + .InitBlogModule(builder.Configuration) + .InitCommentModule(builder.Configuration) + .InitCommentModule(builder.Configuration) + .InitTransactionModule(builder.Configuration) + .RegisterWebDependencies(); + + +#region JWT + +builder.Services.AddJwtAuthentication(builder.Configuration); + +#endregion + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +//if (app.Environment.IsDevelopment()) +//{ +// app.UseSwagger(); +// app.UseSwaggerUI(); +//} + + +app.UseHttpsRedirection(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.UseApiCustomExceptionHandler(); +app.MapControllers(); + +app.UseSwagger(); +app.UseSwaggerUI(options => +{ + // تغییر مسیر به صورت زیر: + options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); +}); +//app.UseSwaggerUI(options => +//{ +// options.SwaggerEndpoint("/DigiLearn.Api/swagger/v1/swagger.json", "My API V1"); +//}); + +app.Run(); diff --git a/src/EndPoints/DigiLearn.WebApi/Properties/launchSettings.json b/src/EndPoints/DigiLearn.WebApi/Properties/launchSettings.json new file mode 100644 index 0000000..eafded2 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:44947", + "sslPort": 44369 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5017", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7148;http://localhost:5017", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/WeatherForecast.cs b/src/EndPoints/DigiLearn.WebApi/WeatherForecast.cs new file mode 100644 index 0000000..3dff7a6 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace DigiLearn.WebApi +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/appsettings.Development.json b/src/EndPoints/DigiLearn.WebApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/appsettings.json b/src/EndPoints/DigiLearn.WebApi/appsettings.json new file mode 100644 index 0000000..6aa73f8 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "JwtConfig": { + "SignInKey": "16D9gnirpoer-4395t-35923892-foeighqae_-4tj32o0E95BdsadasdasdA70-dasdsa", + "Issuer": "DigiLearn.com", + "Audience": "DigiLearn-api" + } +} From 91b6bac8294650386a4288b094a08279fa6d9857 Mon Sep 17 00:00:00 2001 From: mohammad parsa karimi Date: Fri, 6 Jun 2025 00:53:36 +0330 Subject: [PATCH 2/3] Create Notification, Role, User APIs --- DigiLearn.sln | 1 + .../Controllers/UserController.cs | 149 +++++++++++++++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/DigiLearn.sln b/DigiLearn.sln index 3163dad..5468f7c 100644 --- a/DigiLearn.sln +++ b/DigiLearn.sln @@ -181,6 +181,7 @@ Global {9C2E7E12-AC2A-413E-B7DB-12BEC6450A38} = {F4DF9E24-BE80-4499-8986-C615759BEDB3} {4BA50B18-2ACF-4A17-9F72-9E125B9B6CBE} = {B3BD9597-3591-48AF-A287-50339FC5F23A} {7855A4BE-AF70-4196-BE58-661EF9BB7AF9} = {4BA50B18-2ACF-4A17-9F72-9E125B9B6CBE} + {71B252BB-9EBA-4853-BDE3-6A9FC3CD5017} = {CDAFBBC7-33A5-4FE0-BA9D-564B07E2478E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0E3590F8-90D4-4262-8345-AACE391D9339} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs index 8301971..ebc58cd 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs @@ -6,8 +6,22 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; +using System.Drawing; +using Microsoft.AspNetCore.Authorization; +using UserModule.Core.Commands.Notifications.Create; +using UserModule.Core.Commands.Notifications.Delete; +using UserModule.Core.Commands.Roles.Create; +using UserModule.Core.Commands.Roles.Edit; +using UserModule.Core.Commands.Users.ChangeAvatar; +using UserModule.Core.Commands.Users.ChangePassword; +using UserModule.Core.Commands.Users.Edit; +using UserModule.Core.Commands.Users.FullEdit; using UserModule.Core.Commands.Users.Register; +using UserModule.Core.Queries._DTOs; using UserModule.Core.Services; +using UserModule.Data.Entities.Roles; +using UserModule.Core.Commands.Notifications.DeleteAll; +using UserModule.Core.Commands.Notifications.Seen; namespace DigiLearn.WebApi.Controllers { @@ -17,11 +31,15 @@ public class UserController : ApiController { private readonly IConfiguration _configuration; private readonly IUserFacade _facade; + private readonly IRoleFacade _roleFacade; + private readonly INotificationFacade _notificationFacade; - public UserController(IUserFacade facade, IConfiguration configuration) + public UserController(IUserFacade facade, IConfiguration configuration, IRoleFacade roleFacade, INotificationFacade notificationFacade) { _facade = facade; _configuration = configuration; + _roleFacade = roleFacade; + _notificationFacade = notificationFacade; } #region Auth @@ -56,5 +74,134 @@ public async Task> Register([FromBody] RegisterUserCommand comma } #endregion + + #region User + [HttpPatch("EditUserProfile")] + [Authorize] + public async Task EditUserProfile(EditUserCommand command) + { + return CommandResult(await _facade.EditUserProfile(command)); + } + + [HttpPatch("EditUser")] + [Authorize] + public async Task EditUser(FullEditUserCommand command) + { + return CommandResult(await _facade.EditUser(command)); + } + + [HttpPatch("ChangeAvatar")] + [Authorize] + public async Task ChangeAvatar(ChangeUserAvatarCommand command) + { + return CommandResult(await _facade.ChangeAvatar(command)); + } + + [HttpPatch("ChangeUserPassword")] + [Authorize] + public async Task ChangePassword(ChangeUserPasswordCommand command) + { + return CommandResult(await _facade.ChangePassword(command)); + } + + /// + /// + /// + /// + /// + /// نمونه ریکوئست => api/User/GetUserByPhoneNumber?{phoneNumber} + /// + /// + [HttpGet("GetUserByPhoneNumber")] + public async Task> GetUserByPhoneNumber(string phoneNumber) + { + return QueryResult(await _facade.GetUserByPhoneNumber(phoneNumber)); + } + + [HttpGet("GetUserById")] + public async Task> GetUserById(Guid userId) + { + return QueryResult(await _facade.GetById(userId)); + } + + [HttpGet("GetUserByFilter")] + public async Task> GetUserByFilter(UserFilterParams filterParams) + { + return QueryResult(await _facade.GetByFilter(filterParams)); + } + #endregion + + #region Role + + [HttpPost("CreateRole")] + [Authorize] + public async Task CreateRole(CreateRoleCommand command) + { + return CommandResult(await _roleFacade.Create(command)); + } + + [HttpPatch("EditRole")] + [Authorize] + public async Task EditRole(EditRoleCommand command) + { + return CommandResult(await _roleFacade.Edit(command)); + } + + [HttpDelete("DeleteRole")] + [Authorize] + public async Task DeleteRole(Guid roleId) + { + return CommandResult(await _roleFacade.Delete(roleId)); + } + + [HttpGet("GetRoleById")] + [Authorize] + public async Task> GetRoleById(Guid roleId) + { + return QueryResult(await _roleFacade.GetRoleById(roleId)); + } + + [HttpGet("GetAllRoles")] + [Authorize] + public async Task>> GetAllRoles() + { + return QueryResult(await _roleFacade.GetAllRoles()); + } + #endregion + + #region Notification + + [HttpPost("CreateNotification")] + public async Task CreateNotification(CreateNotificationCommand command) + { + return CommandResult(await _notificationFacade.Create(command)); + } + + [HttpDelete("DeleteNotification")] + public async Task DeleteNotification(DeleteNotificationCommand command) + { + return CommandResult(await _notificationFacade.Delete(command)); + } + + [HttpDelete("DeleteAll")] + public async Task DeleteAllNotification(DeleteAllNotificationCommand command) + { + return CommandResult(await _notificationFacade.DeleteAll(command)); + } + + [HttpPatch("SeenNotification")] + public async Task SeenNotification(SeenNotificationCommand command) + { + return CommandResult(await _notificationFacade.Seen(command)); + } + + [HttpGet("GetNotificationByFilter")] + public async Task> GetNotificationByFilter(NotificationFilterParams filterParams) + { + return QueryResult(await _notificationFacade.GetByFilter(filterParams)); + } + + #endregion + } } From 0747f06598692cd52303b8a06d01d3882a01ab68 Mon Sep 17 00:00:00 2001 From: mohammad parsa karimi Date: Sat, 5 Jul 2025 10:18:18 +0330 Subject: [PATCH 3/3] Create WebApi --- .../Controllers/BlogController.cs | 19 +++- .../Controllers/CommentController.cs | 12 ++- .../Controllers/CourseCategoryController.cs | 75 ++++++++++++++ .../Controllers/CourseController.cs | 82 ++++++++++++++++ .../Controllers/OrdersController.cs | 48 +++++++++ .../Controllers/TeacherController.cs | 76 ++++++++++++++ .../Controllers/TicketController.cs | 24 ++++- .../Controllers/TransactionController.cs | 35 ++++++- .../Controllers/UserController.cs | 98 ++++++++++++++----- .../DigiLearn.WebApi/DigiLearn.WebApi.csproj | 2 +- .../Infrastructure/ClaimUtils.cs | 13 +-- .../Security/PermissionChecker.cs | 81 +++++++++++++++ .../Models/{ => Auth}/LoginResultDto.cs | 2 +- .../{ => Auth}/UserLoginCommandViewModel.cs | 2 +- .../Models/Blog/CreatePostViewModel.cs | 13 +++ .../Models/Comment/CreateCommentViewModel.cs | 12 +++ .../Models/Ticket/CreateTicketViewModel.cs | 16 +++ .../Transaction/CreateTransactionViewModel.cs | 21 ++++ .../Models/User/EditUserViewModel.cs | 29 ++++++ .../CreateNotificationViewModel.cs | 16 +++ src/EndPoints/DigiLearn.WebApi/Program.cs | 55 ++++------- 21 files changed, 643 insertions(+), 88 deletions(-) create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/CourseCategoryController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/CourseController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/OrdersController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Controllers/TeacherController.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Infrastructure/Security/PermissionChecker.cs rename src/EndPoints/DigiLearn.WebApi/Models/{ => Auth}/LoginResultDto.cs (68%) rename src/EndPoints/DigiLearn.WebApi/Models/{ => Auth}/UserLoginCommandViewModel.cs (83%) create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/Blog/CreatePostViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/Comment/CreateCommentViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/Ticket/CreateTicketViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/Transaction/CreateTransactionViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/User/EditUserViewModel.cs create mode 100644 src/EndPoints/DigiLearn.WebApi/Models/User/Notification/CreateNotificationViewModel.cs diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs index 304c4ae..cf2f976 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/BlogController.cs @@ -2,9 +2,12 @@ using BlogModule.Services.DTOs.Command; using BlogModule.Services.DTOs.Query; using DigiLearn.WebApi.Infrastructure; +using DigiLearn.WebApi.Infrastructure.Security; +using DigiLearn.WebApi.Models.Blog; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System.Security.Permissions; namespace DigiLearn.WebApi.Controllers { @@ -22,9 +25,18 @@ public BlogController(IBlogService service) #region Blog [Authorize] [HttpPost("CreatePost")] - public async Task CreatePost([FromForm] CreatePostCommand command) + public async Task CreatePost([FromForm] CreatePostViewModel command) { - return CommandResult(await _service.CreatePost(command)); + return CommandResult(await _service.CreatePost(new CreatePostCommand + { + CategoryId = command.CategoryId, + ImageFile = command.ImageFile, + Description = command.Description, + Slug = command.Slug, + OwnerName = command.OwnerName, + Title = command.Title, + UserId = User.GetUserId() + })); } [HttpPatch("EditPost")] @@ -58,7 +70,8 @@ public async Task DeletePost(Guid postId) return QueryResult(await _service.GetPostBySlug(slug)); } [HttpGet("GetPostsByFilter")] - public async Task> GetPostsByFilter([FromQuery]BlogPostFilterParams filterParams) + public async Task> GetPostsByFilter( + [FromQuery] BlogPostFilterParams filterParams) { return QueryResult(await _service.GetPostsByFilter(filterParams)); } diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs index 36d99ad..ef51d83 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/CommentController.cs @@ -3,6 +3,7 @@ using CommentModule.Services; using CommentModule.Services.DTOs; using DigiLearn.WebApi.Infrastructure; +using DigiLearn.WebApi.Models.Comment; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -21,9 +22,16 @@ public CommentController(ICommentService service) [HttpPost("CreateComment")] - public async Task CreateComment(CreateCommentCommand command) + public async Task CreateComment(CreateCommentViewModel command) { - return CommandResult(await _service.CreateComment(command)); + return CommandResult(await _service.CreateComment(new CreateCommentCommand + { + CommentType = command.CommentType, + ParentId = command.ParentId, + EntityId = command.EntityId, + Text = command.Text, + UserId = User.GetUserId(), + })); } [HttpDelete("DeleteComment")] diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/CourseCategoryController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/CourseCategoryController.cs new file mode 100644 index 0000000..9174d8d --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/CourseCategoryController.cs @@ -0,0 +1,75 @@ +using CoreModule.Application.Category.AddChild; +using CoreModule.Application.Category.Create; +using CoreModule.Application.Category.Edit; +using CoreModule.Application.Course.Edit; +using CoreModule.Facade.Category; +using CoreModule.Query.Category._DTOs; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CourseCategoryController : ApiController + { + private readonly ICourseCategoryFacade _facade; + + public CourseCategoryController(ICourseCategoryFacade facade) + { + _facade = facade; + } + + #region Commands + [HttpPost("CreateCourseCategory")] + [Authorize] + public async Task CreateCourseCategory(CreateCategoryCommand command) + { + return CommandResult(await _facade.Create(command)); + } + + [HttpPatch("EditCourseCategory")] + [Authorize] + public async Task EditCourseCategory(EditCategoryCommand command) + { + return CommandResult(await _facade.Edit(command)); + } + + [HttpDelete("DeleteCourseCategory")] + [Authorize] + public async Task DeleteCourseCategory(Guid CourseCategoryId) + { + return CommandResult(await _facade.Delete(CourseCategoryId)); + } + + [HttpPost("AddChildeCourseCategory")] + [Authorize] + public async Task AddChildeCourseCategory(AddChildCategoryCommand command) + { + return CommandResult(await _facade.AddChild(command)); + } + #endregion + + #region Queries + [HttpGet("GetMainCourseCategories")] + public async Task>> GetMainChildeCourseCategories() + { + return QueryResult(await _facade.GetMainCategories()); + } + + [HttpGet("GetByIdCourseCategory")] + public async Task> GetByIdChildeCourseCategory(Guid categoryId) + { + return QueryResult(await _facade.GetById(categoryId)); + } + + [HttpGet("GetChildrenCourseCategory")] + public async Task>> GetChildrenCourseCategory(Guid parentId) + { + return QueryResult(await _facade.GetChildren(parentId)); + } + #endregion + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/CourseController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/CourseController.cs new file mode 100644 index 0000000..5751237 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/CourseController.cs @@ -0,0 +1,82 @@ +using CoreModule.Application.Course.Create; +using CoreModule.Application.Course.Edit; +using CoreModule.Application.Course.Episodes.Add; +using CoreModule.Application.Course.Episodes.Delete; +using CoreModule.Application.Course.Episodes.Edit; +using CoreModule.Application.Course.Sections.AddSection; +using CoreModule.Facade.Course; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CourseController : ApiController + { + private readonly ICourseFacade _facade; + + public CourseController(ICourseFacade facade) + { + _facade = facade; + } + + [HttpPost("CreateCourse")] + [Authorize] + public async Task CreateCourse(CreateCourseCommand command) + { + return CommandResult(await _facade.Create(command)); + } + + [HttpPatch("EditCourse")] + [Authorize] + public async Task EditCourse(EditCourseCommand command) + { + return CommandResult(await _facade.Edit(command)); + } + + [HttpPost("AddCourseSection")] + [Authorize] + public async Task AddCourseSection(AddCourseSectionCommand command) + { + return CommandResult(await _facade.AddSection(command)); + } + + [HttpPost("AddCourseEpisode")] + [Authorize] + public async Task AddCourseEpisode(AddCourseEpisodeCommand command) + { + return CommandResult(await _facade.AddEpisode(command)); + } + + [HttpDelete("DeleteCourseEpisode")] + [Authorize] + public async Task DeleteCourse(DeleteCourseEpisodeCommand command) + { + return CommandResult(await _facade.DeleteEpisode(command)); + } + + [HttpPatch("EditCourseEpisode")] + [Authorize] + public async Task EditCourseEpisode(EditEpisodeCommand command) + { + return CommandResult(await _facade.EditEpisode(command)); + } + + [HttpPost("AddStudent")] + [Authorize] + public async Task AddStudent(Guid courseId) + { + return CommandResult(await _facade.AddStudent(courseId, User.GetUserId())); + } + + [HttpDelete("DeleteStudent")] + [Authorize] + public async Task DeleteStudent(Guid courseId) + { + return CommandResult(await _facade.DeleteStudent(courseId, User.GetUserId())); + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/OrdersController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..7e19492 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/OrdersController.cs @@ -0,0 +1,48 @@ +using CoreModule.Application.Order.AddItem; +using CoreModule.Application.Order.RemoveItem; +using CoreModule.Facade.Orders; +using CoreModule.Query.Order._DTOs; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class OrdersController : ApiController + { + private readonly IOrderFacade _orderFacade; + + public OrdersController(IOrderFacade orderFacade) + { + _orderFacade = orderFacade; + } + + [HttpPost("AddOrderItem")] + public async Task AddItem(AddOrderItemCommand command) + { + return CommandResult(await _orderFacade.AddItem(command)); + } + + [HttpDelete("RemoveOrderItem")] + public async Task RemoveItem(RemoveOrderItemCommand command) + { + return CommandResult(await _orderFacade.RemoveItem(command)); + } + + [HttpPatch("FinallyOrder")] + public async Task FinallyOrder(Guid orderId) + { + return CommandResult(await _orderFacade.FinallyOrder(orderId)); + } + + [HttpGet("GetCurrentOrder")] + [Authorize] + public async Task> GetCurrentOrder() + { + return QueryResult(await _orderFacade.GetCurrentOrder(User.GetUserId())); + } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/TeacherController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/TeacherController.cs new file mode 100644 index 0000000..bd89f6c --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/TeacherController.cs @@ -0,0 +1,76 @@ +using CoreModule.Application.Teacher.AcceptRequest; +using CoreModule.Application.Teacher.Register; +using CoreModule.Application.Teacher.RejectRequest; +using CoreModule.Facade.Teacher; +using CoreModule.Query.Teacher._DTOs; +using DigiLearn.WebApi.Infrastructure; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace DigiLearn.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TeacherController : ApiController + { + private readonly ITeacherFacade _facade; + + public TeacherController(ITeacherFacade facade) + { + _facade = facade; + } + + [HttpPost("RegisterTeacher")] + public async Task RegisterTeacher(RegisterTeacherCommand command) + { + return CommandResult(await _facade.Register(command)); + } + + [HttpPost("AcceptTeacherRequest")] + [Authorize] + public async Task AcceptTeacherRequest(AcceptTeacherRequestCommand command) + { + return CommandResult(await _facade.AcceptRequest(command)); + } + + [HttpDelete("RejectTeacherRequest")] + [Authorize] + public async Task RejectTeacherRequest(RejectTeacherRequestCommand command) + { + return CommandResult(await _facade.RejectRequest(command)); + } + + [HttpPatch("ToggleStatusTeacher")] + [Authorize] + public async Task ToggleStatusTeacher(Guid teacherId) + { + return CommandResult(await _facade.ToggleStatus(teacherId)); + } + + #region Queries + + [HttpGet("GetTeacherById")] + [Authorize] + public async Task> GetTeacherById(Guid id) + { + return QueryResult(await _facade.GetById(id)); + } + + [HttpGet("GetTeacherByUserId")] + [Authorize] + public async Task> GetTeacherByUserId() + { + return QueryResult(await _facade.GetByUserId(User.GetUserId())); + } + + [HttpGet("GetTeacherList")] + [Authorize] + public async Task>> GetTeacherList() + { + return QueryResult(await _facade.GetList()); + } + + #endregion + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs index 004770e..93b5ce2 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/TicketController.cs @@ -1,4 +1,5 @@ using DigiLearn.WebApi.Infrastructure; +using DigiLearn.WebApi.Models.Ticket; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -19,15 +20,30 @@ public TicketController(ITicketService service) } [HttpPost("CreateTicket")] - public async Task> CreateTicket(CreateTicketCommand command) + public async Task> CreateTicket(CreateTicketViewModel command) { - var result = await _service.CreateTicket(command); + var result = await _service.CreateTicket( + new CreateTicketCommand + { + OwnerFullName = command.OwnerFullName, + PhoneNumber = command.PhoneNumber, + Text = command.Text, + Title = command.Title, + UserId = User.GetUserId(), + }); return CommandResult(result); } [HttpPost("SendTicketMessage")] - public async Task SendMessageInTicket(SendTicketMessageCommand command) + public async Task SendMessageInTicket(SendTicketMessageViewModel command) { - var result = await _service.SendMessageInTicket(command); + var result = await _service.SendMessageInTicket( + new SendTicketMessageCommand + { + OwnerFullName = command.OwnerFullName, + Text = command.Text, + TicketId = command.TicketId, + UserId = User.GetUserId(), + }); return CommandResult(result); } [HttpPost("CloseTicket")] diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs index 782f3f4..79fc21a 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/TransactionController.cs @@ -1,4 +1,6 @@ using DigiLearn.WebApi.Infrastructure; +using DigiLearn.WebApi.Models.Transaction; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using TransactionModule.Domain; @@ -20,11 +22,18 @@ public TransactionController(IUserTransactionService service) } #region Commands [HttpPost("CreateTransaction")] - public async Task> CreateTransaction(CreateTransactionCommand command) + public async Task> CreateTransaction(CreateTransactionViewModel command) { try { - Guid transactionId = await _service.CreateTransaction(command); + Guid transactionId = await _service.CreateTransaction(new CreateTransactionCommand + { + LinkId = command.LinkId, + PaymentAmount = command.PaymentAmount, + PaymentGateway = command.PaymentGateway, + TransactionFor = command.TransactionFor, + UserId = User.GetUserId(), + }); return CommandResult(new Common.Application.OperationResult { Message = "عملیات با موفقیت انجام شد.", @@ -96,7 +105,7 @@ public async Task PaymentError(TransactionPaymentErrorCommand command #region Queries - + /// /// نمونه ریکوئست => api/Transaction/GetTransactionById?{transactionId} /// @@ -107,10 +116,26 @@ public async Task> GetTransactionById(Guid transactio } [HttpGet("GetTransactionsByFilter")] + [AllowAnonymous] public async Task> GetTransactionsByFilter - ([FromQuery] UserTransactionFilterParams queryParams) + ([FromQuery] UserTransactionFilterParamsViewModel queryParams) { - return QueryResult(await _service.GetTransactionsByFilter(queryParams)); + return QueryResult(await _service.GetTransactionsByFilter( + new UserTransactionFilterParams + { + EndDate = queryParams.EndDate, + StartDate = queryParams.StartDate, + PageId = queryParams.PageId, + Status = queryParams.Status, + Take = queryParams.Take, + TransactionFor = queryParams.TransactionFor, + UserId = (User.Identity != null && User.Identity.IsAuthenticated) ? + User.GetUserId() : null, + //این میگه که identity مقدارش null نیست + //و اگه یه وقتی identity null باشه خطا + //NullReferenceException میده + //UserId = User.Identity!.IsAuthenticated ? User.GetUserId() : null, + })); } [HttpGet("GetCancelTransactionsCount")] diff --git a/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs index ebc58cd..e0fcb8c 100644 --- a/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs +++ b/src/EndPoints/DigiLearn.WebApi/Controllers/UserController.cs @@ -2,7 +2,6 @@ using Common.Application.SecurityUtil; using DigiLearn.WebApi.Infrastructure; using DigiLearn.WebApi.Infrastructure.JwtUtils; -using DigiLearn.WebApi.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; @@ -22,6 +21,10 @@ using UserModule.Data.Entities.Roles; using UserModule.Core.Commands.Notifications.DeleteAll; using UserModule.Core.Commands.Notifications.Seen; +using DigiLearn.WebApi.Models.Auth; +using DigiLearn.WebApi.Models.User; +using DigiLearn.WebApi.Models.User.Notification; +using DigiLearn.WebApi.Infrastructure.Security; namespace DigiLearn.WebApi.Controllers { @@ -78,30 +81,54 @@ public async Task> Register([FromBody] RegisterUserCommand comma #region User [HttpPatch("EditUserProfile")] [Authorize] - public async Task EditUserProfile(EditUserCommand command) + public async Task EditUserProfile(EditUserViewModel command) { - return CommandResult(await _facade.EditUserProfile(command)); + return CommandResult(await _facade.EditUserProfile(new EditUserCommand + { + Email = command.Email, + Family = command.Family, + Name = command.Name, + UserId = User.GetUserId(), + })); } [HttpPatch("EditUser")] [Authorize] - public async Task EditUser(FullEditUserCommand command) + public async Task EditUser(FullEditUserViewModel command) { - return CommandResult(await _facade.EditUser(command)); + return CommandResult(await _facade.EditUser(new FullEditUserCommand + { + Email = command.Email, + Family = command.Family, + Name = command.Name, + Password = command.Password, + PhoneNumber = command.PhoneNumber, + Roles = command.Roles, + UserId = User.GetUserId() + })); } [HttpPatch("ChangeAvatar")] [Authorize] - public async Task ChangeAvatar(ChangeUserAvatarCommand command) + public async Task ChangeAvatar(ChangeUserAvatarViewModel command) { - return CommandResult(await _facade.ChangeAvatar(command)); + return CommandResult(await _facade.ChangeAvatar(new ChangeUserAvatarCommand + { + AvatarFile = command.AvatarFile, + UserId = User.GetUserId() + })); } [HttpPatch("ChangeUserPassword")] [Authorize] - public async Task ChangePassword(ChangeUserPasswordCommand command) + public async Task ChangePassword(ChangeUserPasswordViewModel command) { - return CommandResult(await _facade.ChangePassword(command)); + return CommandResult(await _facade.ChangePassword(new ChangeUserPasswordCommand + { + CurrentPassword = command.CurrentPassword, + NewPassword = command.NewPassword, + UserId = User.GetUserId() + })); } /// @@ -113,18 +140,22 @@ public async Task ChangePassword(ChangeUserPasswordCommand command) /// /// [HttpGet("GetUserByPhoneNumber")] + [Authorize] public async Task> GetUserByPhoneNumber(string phoneNumber) { return QueryResult(await _facade.GetUserByPhoneNumber(phoneNumber)); } [HttpGet("GetUserById")] - public async Task> GetUserById(Guid userId) + [Authorize] + public async Task> GetUserById() { - return QueryResult(await _facade.GetById(userId)); + return QueryResult(await _facade.GetById(User.GetUserId())); } [HttpGet("GetUserByFilter")] + [Authorize] + [PermissionChecker(UserModule.Data.Entities._Enums.Permissions.مدیریت_کاربران)] public async Task> GetUserByFilter(UserFilterParams filterParams) { return QueryResult(await _facade.GetByFilter(filterParams)); @@ -170,37 +201,54 @@ public async Task>> GetAllRoles() #endregion #region Notification - + [HttpPost("CreateNotification")] - public async Task CreateNotification(CreateNotificationCommand command) + public async Task CreateNotification(CreateNotificationViewModel command) { - return CommandResult(await _notificationFacade.Create(command)); + return CommandResult(await _notificationFacade.Create(new CreateNotificationCommand + { + Text = command.Title, + Title = command.Title, + UserId = User.GetUserId() + })); } - + [HttpDelete("DeleteNotification")] - public async Task DeleteNotification(DeleteNotificationCommand command) + [Authorize] + public async Task DeleteNotification(DeleteNotificationViewModel command) { - return CommandResult(await _notificationFacade.Delete(command)); + return CommandResult(await _notificationFacade.Delete( + new DeleteNotificationCommand(command.NotificationId, User.GetUserId()))); } - + [HttpDelete("DeleteAll")] - public async Task DeleteAllNotification(DeleteAllNotificationCommand command) + [Authorize] + public async Task DeleteAllNotification() { - return CommandResult(await _notificationFacade.DeleteAll(command)); + return CommandResult(await _notificationFacade.DeleteAll( + new DeleteAllNotificationCommand(User.GetUserId()))); } - + [HttpPatch("SeenNotification")] public async Task SeenNotification(SeenNotificationCommand command) { return CommandResult(await _notificationFacade.Seen(command)); } - + [HttpGet("GetNotificationByFilter")] - public async Task> GetNotificationByFilter(NotificationFilterParams filterParams) + public async Task> GetNotificationByFilter( + NotificationFilterParamsViewModel filterParams) { - return QueryResult(await _notificationFacade.GetByFilter(filterParams)); + return QueryResult(await _notificationFacade.GetByFilter( + new NotificationFilterParams + { + IsSeen = filterParams.IsSeen, + PageId = filterParams.PageId, + Take = filterParams.Take, + UserId = User.GetUserId() + })); } - + #endregion } diff --git a/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj index 84d8085..f685dff 100644 --- a/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj +++ b/src/EndPoints/DigiLearn.WebApi/DigiLearn.WebApi.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs index c35daba..f9dae44 100644 --- a/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/ClaimUtils.cs @@ -5,21 +5,16 @@ namespace DigiLearn.WebApi.Infrastructure; public static class ClaimUtils { - public static long GetUserId(this ClaimsPrincipal principal) + public static Guid GetUserId(this ClaimsPrincipal principal) { if (principal == null) throw new ArgumentNullException(nameof(principal)); - return Convert.ToInt64(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value); + var idClaim = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; + return Guid.TryParse(idClaim, out var userId) ? userId + : throw new InvalidOperationException("Invalid GUID format."); } - public static string GetUserIdToString(this ClaimsPrincipal principal) - { - if (principal == null) - throw new ArgumentNullException(nameof(principal)); - //return Convert.ToString(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value); - return Convert.ToString(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value)!; - } public static string GetUserName(this ClaimsPrincipal principal) { if (principal == null) diff --git a/src/EndPoints/DigiLearn.WebApi/Infrastructure/Security/PermissionChecker.cs b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Security/PermissionChecker.cs new file mode 100644 index 0000000..733cc68 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Infrastructure/Security/PermissionChecker.cs @@ -0,0 +1,81 @@ +using AngleSharp.Html.LinkRels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using UserModule.Core.Services; +using UserModule.Data.Entities._Enums; +using UserModule.Data.Entities.Roles; + +namespace DigiLearn.WebApi.Infrastructure.Security; + +public class PermissionChecker : AuthorizeAttribute, IAsyncAuthorizationFilter +{ + private IUserFacade _userFacade = null!; + private IRoleFacade _roleFacade = null!; + private readonly Permissions _permission; + + public PermissionChecker(Permissions permission) + { + _permission = permission; + } + + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + + _userFacade = context.HttpContext.RequestServices.GetRequiredService(); + _roleFacade = context.HttpContext.RequestServices.GetRequiredService(); + if (context.HttpContext.User.Identity != null && context.HttpContext.User.Identity.IsAuthenticated) + { + if (await UserHasPermission(context) == false) + { + context.Result = new ForbidResult(); + } + } + else + { + context.Result = new UnauthorizedObjectResult("Unauthorize"); + } + } + + private bool HasAllowAnonymous(AuthorizationFilterContext context) + { + if (_roleFacade == null && _userFacade == null) + { + return false; + throw new Exception("مشکل سمت سرور به وجود آمده"); + } + var metaData = context.ActionDescriptor.EndpointMetadata.OfType().ToList(); + bool hasAllowAnonymous = false; + foreach (var f in metaData) + { + try + { + hasAllowAnonymous = f.TypeId.Name == "AllowAnonymousAttribute"; + if (hasAllowAnonymous) + break; + } + catch + { + // ignored + } + } + + return hasAllowAnonymous; + } + private async Task UserHasPermission(AuthorizationFilterContext context) + { + var user = await _userFacade.GetUserByPhoneNumber(context.HttpContext.User.GetPhoneNumber()); + if (user == null) + return false; + + var roleNames = user.Roles.Select(s => s.Title).ToList(); + var roles = await _roleFacade.GetAllRoles(); + if (roles == null) + { + return false; + } + var userRoles = roles.Where(i => roleNames.Contains(i.Name)).ToList(); + + return userRoles.Any(i => i.Permissions.Any(s=>s.Permissions.Equals(_permission))); + } +} \ No newline at end of file diff --git a/src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs b/src/EndPoints/DigiLearn.WebApi/Models/Auth/LoginResultDto.cs similarity index 68% rename from src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs rename to src/EndPoints/DigiLearn.WebApi/Models/Auth/LoginResultDto.cs index bd1d7b7..8f578f5 100644 --- a/src/EndPoints/DigiLearn.WebApi/Models/LoginResultDto.cs +++ b/src/EndPoints/DigiLearn.WebApi/Models/Auth/LoginResultDto.cs @@ -1,4 +1,4 @@ -namespace DigiLearn.WebApi.Models +namespace DigiLearn.WebApi.Models.Auth { public class LoginResultDto { diff --git a/src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/Auth/UserLoginCommandViewModel.cs similarity index 83% rename from src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs rename to src/EndPoints/DigiLearn.WebApi/Models/Auth/UserLoginCommandViewModel.cs index 7746e53..60524fc 100644 --- a/src/EndPoints/DigiLearn.WebApi/Models/UserLoginCommandViewModel.cs +++ b/src/EndPoints/DigiLearn.WebApi/Models/Auth/UserLoginCommandViewModel.cs @@ -1,4 +1,4 @@ -namespace DigiLearn.WebApi.Models +namespace DigiLearn.WebApi.Models.Auth { public class UserLoginCommandViewModel { diff --git a/src/EndPoints/DigiLearn.WebApi/Models/Blog/CreatePostViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/Blog/CreatePostViewModel.cs new file mode 100644 index 0000000..b65bd4b --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/Blog/CreatePostViewModel.cs @@ -0,0 +1,13 @@ +namespace DigiLearn.WebApi.Models.Blog +{ + public class CreatePostViewModel + { + public string Title { get; set; } + public string OwnerName { get; set; } + public string Description { get; set; } + public string Slug { get; set; } + public IFormFile ImageFile { get; set; } + public Guid CategoryId { get; set; } + } + +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/Comment/CreateCommentViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/Comment/CreateCommentViewModel.cs new file mode 100644 index 0000000..a9d16ea --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/Comment/CreateCommentViewModel.cs @@ -0,0 +1,12 @@ +using CommentModule.Domain; + +namespace DigiLearn.WebApi.Models.Comment +{ + public class CreateCommentViewModel + { + public string Text { get; set; } + public Guid? ParentId { get; set; } = null; + public Guid EntityId { get; set; } + public CommentType CommentType { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/Ticket/CreateTicketViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/Ticket/CreateTicketViewModel.cs new file mode 100644 index 0000000..76c5613 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/Ticket/CreateTicketViewModel.cs @@ -0,0 +1,16 @@ +namespace DigiLearn.WebApi.Models.Ticket +{ + public class CreateTicketViewModel + { + public string OwnerFullName { get; set; } + public string PhoneNumber { get; set; } + public string Title { get; set; } + public string Text { get; set; } + } + public class SendTicketMessageViewModel + { + public Guid TicketId { get; set; } + public string OwnerFullName { get; set; } + public string Text { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/Transaction/CreateTransactionViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/Transaction/CreateTransactionViewModel.cs new file mode 100644 index 0000000..3a16512 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/Transaction/CreateTransactionViewModel.cs @@ -0,0 +1,21 @@ +using TransactionModule.Domain; + +namespace DigiLearn.WebApi.Models.Transaction +{ + public class CreateTransactionViewModel + { + public int PaymentAmount { get; set; } + public Guid LinkId { get; set; } + public PaymentGateway PaymentGateway { get; set; } + public TransactionFor TransactionFor { get; set; } + } + public class UserTransactionFilterParamsViewModel + { + public int PageId { get; set; } = 1; + public int Take { get; set; } = 20; + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public TransactionStatus? Status { get; set; } + public TransactionFor? TransactionFor { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/User/EditUserViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/User/EditUserViewModel.cs new file mode 100644 index 0000000..9f7d04c --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/User/EditUserViewModel.cs @@ -0,0 +1,29 @@ +using Common.Application; + +namespace DigiLearn.WebApi.Models.User +{ + public class EditUserViewModel + { + public string Name { get; set; } + public string Family { get; set; } + public string? Email { get; set; } + } + public class FullEditUserViewModel + { + public string? Name { get; set; } + public string? Family { get; set; } + public string? Email { get; set; } + public string PhoneNumber { get; set; } + public string? Password { get; set; } + public List Roles { get; set; } + } + public class ChangeUserAvatarViewModel + { + public IFormFile AvatarFile { get; set; } + } + public class ChangeUserPasswordViewModel + { + public string CurrentPassword { get; set; } + public string NewPassword { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Models/User/Notification/CreateNotificationViewModel.cs b/src/EndPoints/DigiLearn.WebApi/Models/User/Notification/CreateNotificationViewModel.cs new file mode 100644 index 0000000..b682537 --- /dev/null +++ b/src/EndPoints/DigiLearn.WebApi/Models/User/Notification/CreateNotificationViewModel.cs @@ -0,0 +1,16 @@ +using Common.Application; +using Common.Query.Filter; + +namespace DigiLearn.WebApi.Models.User.Notification +{ + public class CreateNotificationViewModel + { + public string Text { get; set; } + public string Title { get; set; } + } + public record DeleteNotificationViewModel(Guid NotificationId); + public class NotificationFilterParamsViewModel : BaseFilterParam + { + public bool? IsSeen { get; set; } + } +} diff --git a/src/EndPoints/DigiLearn.WebApi/Program.cs b/src/EndPoints/DigiLearn.WebApi/Program.cs index dcf8576..77a3d18 100644 --- a/src/EndPoints/DigiLearn.WebApi/Program.cs +++ b/src/EndPoints/DigiLearn.WebApi/Program.cs @@ -19,33 +19,8 @@ var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -//builder.Services.AddControllers() -// .ConfigureApiBehaviorOptions(option => -// { -// option.InvalidModelStateResponseFactory = (context => -// { -// var result = new ApiResult() -// { -// IsSuccess = false, -// MetaData = new() -// { -// AppStatusCode = AppStatusCode.BadRequest, -// Message = ModelStateUtil.GetModelStateErrors(context.ModelState) -// } -// }; -// return new BadRequestObjectResult(result); -// }); -// }); -builder.Services.AddControllers(options => // تغییرات در این بخش -{ - // افزودن فیلتر جهانی [Authorize] - var policy = new AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - options.Filters.Add(new AuthorizeFilter(policy)); -}) +builder.Services.AddControllers() .ConfigureApiBehaviorOptions(option => { option.InvalidModelStateResponseFactory = (context) => @@ -124,7 +99,6 @@ .InitCoreModule(builder.Configuration) .InitBlogModule(builder.Configuration) .InitCommentModule(builder.Configuration) - .InitCommentModule(builder.Configuration) .InitTransactionModule(builder.Configuration) .RegisterWebDependencies(); @@ -134,7 +108,13 @@ builder.Services.AddJwtAuthentication(builder.Configuration); #endregion - +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); +}); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -150,18 +130,19 @@ app.UseAuthentication(); app.UseAuthorization(); + +app.UseCors("AllowAll"); + app.UseApiCustomExceptionHandler(); app.MapControllers(); -app.UseSwagger(); -app.UseSwaggerUI(options => +if (app.Environment.IsDevelopment()) { - // تغییر مسیر به صورت زیر: - options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); -}); -//app.UseSwaggerUI(options => -//{ -// options.SwaggerEndpoint("/DigiLearn.Api/swagger/v1/swagger.json", "My API V1"); -//}); + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + }); +} app.Run();