diff --git a/src/Application/DTOs/Profile/ChangePassword/ChangePasswordRequest.cs b/src/Application/DTOs/Profile/ChangePasswordRequest.cs similarity index 78% rename from src/Application/DTOs/Profile/ChangePassword/ChangePasswordRequest.cs rename to src/Application/DTOs/Profile/ChangePasswordRequest.cs index 3ad7a16..e67a244 100644 --- a/src/Application/DTOs/Profile/ChangePassword/ChangePasswordRequest.cs +++ b/src/Application/DTOs/Profile/ChangePasswordRequest.cs @@ -1,8 +1,8 @@ -namespace Application.DTOs.Profile.ChangePassword; - -public class ChangePasswordRequest -{ - public string UserId { get; set; } = string.Empty; - public string CurrentPassword { get; set; } = string.Empty; - public string NewPassword { get; set; } = string.Empty; +namespace Application.DTOs.Profile; + +public class ChangePasswordRequest +{ + public string UserId { get; set; } = string.Empty; + public string CurrentPassword { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Application/DTOs/User/ChangeRole/ChangeRoleRequest.cs b/src/Application/DTOs/User/ChangeRoleRequest.cs similarity index 73% rename from src/Application/DTOs/User/ChangeRole/ChangeRoleRequest.cs rename to src/Application/DTOs/User/ChangeRoleRequest.cs index 7cbce05..c4518e5 100644 --- a/src/Application/DTOs/User/ChangeRole/ChangeRoleRequest.cs +++ b/src/Application/DTOs/User/ChangeRoleRequest.cs @@ -1,4 +1,4 @@ -namespace Application.DTOs.Identity.ChangeRole; +namespace Application.DTOs.User; public class ChangeRoleRequest { diff --git a/src/Application/DTOs/User/CreateUser/CreateUserResponse.cs b/src/Application/DTOs/User/CreateUser/CreateUserResponse.cs index 8b25986..9ab3f0e 100644 --- a/src/Application/DTOs/User/CreateUser/CreateUserResponse.cs +++ b/src/Application/DTOs/User/CreateUser/CreateUserResponse.cs @@ -7,4 +7,4 @@ public class CreateUserResponse public string Email { get; set; } = String.Empty; public string UserName { get; set; } = String.Empty; public string Role { get; set; } = String.Empty; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Application/DTOs/User/GetUser/GetUserResponse.cs b/src/Application/DTOs/User/GetUserResponse.cs similarity index 86% rename from src/Application/DTOs/User/GetUser/GetUserResponse.cs rename to src/Application/DTOs/User/GetUserResponse.cs index b05fb29..7d46117 100644 --- a/src/Application/DTOs/User/GetUser/GetUserResponse.cs +++ b/src/Application/DTOs/User/GetUserResponse.cs @@ -1,4 +1,4 @@ -namespace Application.DTOs.Identity.GetUser; +namespace Application.DTOs.User; public class GetUserResponse { diff --git a/src/Application/Mappers/UserMapper.cs b/src/Application/Mappers/UserMapper.cs index e485f93..1c08c39 100644 --- a/src/Application/Mappers/UserMapper.cs +++ b/src/Application/Mappers/UserMapper.cs @@ -1,6 +1,6 @@ using Application.DTOs.Identity.CreateUser; -using Application.DTOs.Identity.GetUser; using Application.DTOs.Identity.LoginUser; +using Application.DTOs.User; using Domain.Entities; namespace Application.Mappers; diff --git a/src/Web/Identity/Claims.cs b/src/Web/AccessControl/Claims.cs similarity index 79% rename from src/Web/Identity/Claims.cs rename to src/Web/AccessControl/Claims.cs index 815b69b..5207848 100644 --- a/src/Web/Identity/Claims.cs +++ b/src/Web/AccessControl/Claims.cs @@ -1,7 +1,7 @@ -namespace Web.Identity; - -public static class Claims -{ - public const string UserId = nameof(UserId); - public const string Role = nameof(Role); +namespace Web.AccessControl; + +public static class Claims +{ + public const string UserId = nameof(UserId); + public const string Role = nameof(Role); } \ No newline at end of file diff --git a/src/Web/Identity/RequiresClaimAttribute.cs b/src/Web/AccessControl/RequiresClaimAttribute.cs similarity index 94% rename from src/Web/Identity/RequiresClaimAttribute.cs rename to src/Web/AccessControl/RequiresClaimAttribute.cs index 656ee41..679e2e3 100644 --- a/src/Web/Identity/RequiresClaimAttribute.cs +++ b/src/Web/AccessControl/RequiresClaimAttribute.cs @@ -1,35 +1,35 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace Web.Identity; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class RequiresAnyRoleAttribute : Attribute, IAuthorizationFilter -{ - private readonly string _claimName; - private readonly string[] _roles; - - public RequiresAnyRoleAttribute(string claimName, params string[] roles) - { - _claimName = claimName; - _roles = roles; - } - - public void OnAuthorization(AuthorizationFilterContext context) - { - var user = context.HttpContext.User; - - if (user.Identity is { IsAuthenticated: false }) - { - context.Result = new UnauthorizedResult(); - return; - } - - var hasRequiredRole = _roles.Any(role => user.HasClaim(_claimName, role)); - - if (!hasRequiredRole) - { - context.Result = new ForbidResult(); - } - } +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Web.AccessControl; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class RequiresAnyRoleAttribute : Attribute, IAuthorizationFilter +{ + private readonly string _claimName; + private readonly string[] _roles; + + public RequiresAnyRoleAttribute(string claimName, params string[] roles) + { + _claimName = claimName; + _roles = roles; + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + var user = context.HttpContext.User; + + if (user.Identity is { IsAuthenticated: false }) + { + context.Result = new UnauthorizedResult(); + return; + } + + var hasRequiredRole = _roles.Any(role => user.HasClaim(_claimName, role)); + + if (!hasRequiredRole) + { + context.Result = new ForbidResult(); + } + } } \ No newline at end of file diff --git a/src/Web/Controllers/AccountsController.cs b/src/Web/Controllers/AccountsController.cs index f165282..58d04e0 100644 --- a/src/Web/Controllers/AccountsController.cs +++ b/src/Web/Controllers/AccountsController.cs @@ -3,8 +3,8 @@ using Domain.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Web.AccessControl; using Web.Helper; -using Web.Identity; using Web.Mappers; namespace Web.Controllers; @@ -40,10 +40,11 @@ public async Task UploadAccounts([FromForm] IFormFile file) var result = await _accountService.AddAccountsFromCsvAsync(filePath); if (!result.Succeed) { - return BadRequest(result.Message); + var errorResponse = Errors.New(nameof(UploadAccounts), result.Message); + return BadRequest(errorResponse); } - return Ok(); + return Ok("Accounts uploaded successfully!"); } [HttpGet("{accountId}")] @@ -54,12 +55,15 @@ public async Task UploadAccounts([FromForm] IFormFile file) public async Task GetAccountById(long accountId) { var account = await _accountService.GetAccountByIdAsync(accountId); - if (account == null) + if (!account.Succeed) { - return NotFound(); + var errorResponse = Errors.New(nameof(GetAccountById), account.Message); + return NotFound(errorResponse); } - return Ok(account.ToAccountDto()); + var response = account.Value!; + + return Ok(response.ToAccountDto()); } [HttpGet] @@ -72,7 +76,8 @@ public async Task GetAllAccounts() var allAccounts = await _accountService.GetAllAccountsAsync(); if (!allAccounts.Succeed) { - return BadRequest(Errors.New(nameof(GetAllAccounts), allAccounts.Message)); + var errorResponse = Errors.New(nameof(GetAllAccounts), allAccounts.Message); + return BadRequest(errorResponse); } var response = allAccounts.Value!; diff --git a/src/Web/Controllers/ProfileController.cs b/src/Web/Controllers/ProfileController.cs index aafd0d9..cd6d470 100644 --- a/src/Web/Controllers/ProfileController.cs +++ b/src/Web/Controllers/ProfileController.cs @@ -3,9 +3,9 @@ using Application.Interfaces.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Web.AccessControl; using Web.DTOs.Profile; using Web.Helper; -using Web.Identity; using Web.Mappers; namespace Web.Controllers; @@ -35,7 +35,8 @@ public async Task EditProfileInfo([FromBody] EditProfileInfoDto e if (!result.Succeed) { - return BadRequest(Errors.New(nameof(EditProfileInfo), result.Message)); + var errorResponse = Errors.New(nameof(EditProfileInfo), result.Message); + return BadRequest(errorResponse); } return Ok("Profile info updated successfully!"); @@ -55,7 +56,8 @@ public async Task GetProfileInfo() if (!result.Succeed) { - return NotFound(Errors.New(nameof(GetProfileInfo), "User not found!")); + var errorResponse = Errors.New(nameof(GetProfileInfo), result.Message); + return NotFound(errorResponse); } var user = result.Value!; @@ -77,7 +79,8 @@ public async Task ChangePassword([FromBody] ChangePasswordDto cha if (!result.Succeed) { - return BadRequest(Errors.New(nameof(ChangePassword), result.Message)); + var errorResponse = Errors.New(nameof(ChangePassword), result.Message); + return BadRequest(errorResponse); } return Ok("Password changed successfully!"); diff --git a/src/Web/Controllers/TransactionsController.cs b/src/Web/Controllers/TransactionsController.cs index f744bd0..3efb8d1 100644 --- a/src/Web/Controllers/TransactionsController.cs +++ b/src/Web/Controllers/TransactionsController.cs @@ -2,8 +2,8 @@ using Domain.Constants; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Web.AccessControl; using Web.Helper; -using Web.Identity; using Web.Mappers; namespace Web.Controllers; @@ -42,10 +42,11 @@ public async Task UploadTransactions([FromForm] IFormFile file) if (!result.Succeed) { - return BadRequest(result.Message); + var errorResponse = Errors.New(nameof(UploadTransactions), result.Message); + return BadRequest(errorResponse); } - return Ok(); + return Ok("Transactions uploaded successfully!"); } [HttpGet()] @@ -60,7 +61,8 @@ public async Task GetAllTransactions() var allTransactions = await _transactionService.GetAllTransactionsAsync(); if (!allTransactions.Succeed) { - return BadRequest(Errors.New(nameof(GetAllTransactions), allTransactions.Message)); + var errorResponse = Errors.New(nameof(GetAllTransactions), allTransactions.Message); + return BadRequest(errorResponse); } var response = allTransactions.Value!; @@ -79,7 +81,8 @@ public async Task GetTransactionsByAccountId(long accountId) if (!transactions.Succeed) { - return BadRequest(Errors.New(nameof(GetAllTransactions), transactions.Message)); + var errorResponse = Errors.New(nameof(GetAllTransactions), transactions.Message); + return BadRequest(errorResponse); } var response = transactions.Value!; diff --git a/src/Web/Controllers/UsersController.cs b/src/Web/Controllers/UsersController.cs index 9391b52..51f4921 100644 --- a/src/Web/Controllers/UsersController.cs +++ b/src/Web/Controllers/UsersController.cs @@ -2,15 +2,17 @@ using Domain.Constants; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Web.DTOs.Identity; +using Web.AccessControl; +using Web.DTOs.User; +using Web.DTOs.User.Login; +using Web.DTOs.User.Signup; using Web.Helper; -using Web.Identity; using Web.Mappers; namespace Web.Controllers; [ApiController] -[Route("identity")] +[Route("users")] public class UsersController : ControllerBase { private readonly IUserService _userService; @@ -28,11 +30,13 @@ public UsersController(IUserService userService) [ProducesResponseType(403)] public async Task Signup([FromBody] SignupDto signupDto) { - var result = await _userService.SignUpUser(signupDto.ToCreateUserRequest()); + var result = await _userService.SignUp(signupDto.ToCreateUserRequest()); if (!result.Succeed) { - return BadRequest(Errors.New(nameof(Signup), result.Message)); + var errorResponse = Errors.New(nameof(Signup), result.Message); + return BadRequest(errorResponse); + // return StatusCode(500, Errors.New("Server Error", $"An unexpected error occurred: {ex.Message}")); } var response = result.Value!; @@ -49,7 +53,8 @@ public async Task Login([FromBody] LoginDto loginDto) if (!result.Succeed) { - return Unauthorized(Errors.New(nameof(Login), result.Message)); + var errorResponse = Errors.New(nameof(Login), result.Message); + return Unauthorized(errorResponse); } var response = result.Value!; @@ -70,7 +75,8 @@ public async Task ChangeRole([FromBody] ChangeRoleDto changeRoleD if (!result.Succeed) { - return BadRequest(Errors.New(nameof(ChangeRole), result.Message)); + var errorResponse = Errors.New(nameof(ChangeRole), result.Message); + return BadRequest(errorResponse); } return Ok("Role changed successfully!"); @@ -82,10 +88,18 @@ public async Task ChangeRole([FromBody] ChangeRoleDto changeRoleD [ProducesResponseType(200)] [ProducesResponseType(401)] [ProducesResponseType(403)] - public async Task GetUsersAsync() + public async Task GetAllUsers() { - var appUsersWithRoles = await _userService.GetUsersAsync(); + var usersWithRolesResult = await _userService.GetAllUsersAsync(); - return Ok(appUsersWithRoles); + if (!usersWithRolesResult.Succeed) + { + var errorResponse = Errors.New(nameof(ChangeRole), usersWithRolesResult.Message); + return BadRequest(errorResponse); + } + + var response = usersWithRolesResult.Value!; + + return Ok(response); } } \ No newline at end of file diff --git a/src/Web/DTOs/User/ChangeRoleDto.cs b/src/Web/DTOs/User/ChangeRoleDto.cs index c0396c9..d9c10da 100644 --- a/src/Web/DTOs/User/ChangeRoleDto.cs +++ b/src/Web/DTOs/User/ChangeRoleDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Web.DTOs.Identity; +namespace Web.DTOs.User; public class ChangeRoleDto { diff --git a/src/Web/DTOs/User/Login/LoginDto.cs b/src/Web/DTOs/User/Login/LoginDto.cs index 35809b6..2b67595 100644 --- a/src/Web/DTOs/User/Login/LoginDto.cs +++ b/src/Web/DTOs/User/Login/LoginDto.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; -using System.Runtime.InteropServices.JavaScript; -namespace Web.DTOs.Identity; +namespace Web.DTOs.User.Login; public class LoginDto { diff --git a/src/Web/DTOs/User/Login/UserLoggedInDto.cs b/src/Web/DTOs/User/Login/LoginResponseDto.cs similarity index 84% rename from src/Web/DTOs/User/Login/UserLoggedInDto.cs rename to src/Web/DTOs/User/Login/LoginResponseDto.cs index 8ef5237..1f401f0 100644 --- a/src/Web/DTOs/User/Login/UserLoggedInDto.cs +++ b/src/Web/DTOs/User/Login/LoginResponseDto.cs @@ -1,6 +1,6 @@ -namespace Web.DTOs.Identity; +namespace Web.DTOs.User.Login; -public class UserLoggedInDto +public class LoginResponseDto { public string FirstName { get; set; } = String.Empty; public string LastName { get; set; } = String.Empty; diff --git a/src/Web/DTOs/User/Signup/SignupDto.cs b/src/Web/DTOs/User/Signup/SignupDto.cs index 1ef451a..f1b8438 100644 --- a/src/Web/DTOs/User/Signup/SignupDto.cs +++ b/src/Web/DTOs/User/Signup/SignupDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Web.DTOs.Identity; +namespace Web.DTOs.User.Signup; public class SignupDto { diff --git a/src/Web/DTOs/User/Signup/UserSignedUpDto.cs b/src/Web/DTOs/User/Signup/SignupResponseDto.cs similarity index 81% rename from src/Web/DTOs/User/Signup/UserSignedUpDto.cs rename to src/Web/DTOs/User/Signup/SignupResponseDto.cs index d5dc30d..0a81338 100644 --- a/src/Web/DTOs/User/Signup/UserSignedUpDto.cs +++ b/src/Web/DTOs/User/Signup/SignupResponseDto.cs @@ -1,6 +1,6 @@ -namespace Web.DTOs.Identity; +namespace Web.DTOs.User.Signup; -public class UserSignedUpDto +public class SignupResponseDto { public string FirstName { get; set; } = String.Empty; public string LastName { get; set; } = String.Empty; diff --git a/src/Web/Mappers/ProfileMapper.cs b/src/Web/Mappers/ProfileMapper.cs index cafb24e..efcf45f 100644 --- a/src/Web/Mappers/ProfileMapper.cs +++ b/src/Web/Mappers/ProfileMapper.cs @@ -1,4 +1,4 @@ -using Application.DTOs.Profile.ChangePassword; +using Application.DTOs.Profile; using Application.DTOs.Profile.EditProfile; using Application.DTOs.Profile.GetProfileInfo; using Web.DTOs.Profile; diff --git a/src/Web/Mappers/UserMapper.cs b/src/Web/Mappers/UserMapper.cs index c194a9e..5308686 100644 --- a/src/Web/Mappers/UserMapper.cs +++ b/src/Web/Mappers/UserMapper.cs @@ -1,8 +1,10 @@ using Application.DTOs.Identity; -using Application.DTOs.Identity.ChangeRole; using Application.DTOs.Identity.CreateUser; using Application.DTOs.Identity.LoginUser; -using Web.DTOs.Identity; +using Application.DTOs.User; +using Web.DTOs.User; +using Web.DTOs.User.Login; +using Web.DTOs.User.Signup; namespace Web.Mappers; @@ -31,9 +33,9 @@ public static LoginUserRequest ToLoginUserRequest(this LoginDto loginDto) }; } - public static UserSignedUpDto ToUserSignedUpDto(this CreateUserResponse createUserResponse) + public static SignupResponseDto ToUserSignedUpDto(this CreateUserResponse createUserResponse) { - return new UserSignedUpDto + return new SignupResponseDto { FirstName = createUserResponse.FirstName, LastName = createUserResponse.LastName, @@ -43,9 +45,9 @@ public static UserSignedUpDto ToUserSignedUpDto(this CreateUserResponse createUs }; } - public static UserLoggedInDto ToUserLoggedInDto(this LoginUserResponse loginUserResponse) + public static LoginResponseDto ToUserLoggedInDto(this LoginUserResponse loginUserResponse) { - return new UserLoggedInDto + return new LoginResponseDto { FirstName = loginUserResponse.FirstName, LastName = loginUserResponse.LastName, diff --git a/test/Web.UnitTests/Controllers/UsersControllerTests.cs b/test/Web.UnitTests/Controllers/UsersControllerTests.cs index 836d6d6..e7b6aec 100644 --- a/test/Web.UnitTests/Controllers/UsersControllerTests.cs +++ b/test/Web.UnitTests/Controllers/UsersControllerTests.cs @@ -1,9 +1,9 @@ using System.Security.Claims; using Application.DTOs; using Application.DTOs.Identity; -using Application.DTOs.Identity.ChangeRole; using Application.DTOs.Identity.CreateUser; using Application.DTOs.Identity.LoginUser; +using Application.DTOs.User; using Application.Interfaces.Services; using Domain.Constants; using Domain.Entities; @@ -11,7 +11,9 @@ using Microsoft.AspNetCore.Mvc; using NSubstitute; using Web.Controllers; -using Web.DTOs.Identity; +using Web.DTOs.User; +using Web.DTOs.User.Login; +using Web.DTOs.User.Signup; using Web.Mappers; using Web.Models; using Xunit.Abstractions; @@ -45,7 +47,7 @@ public async Task Signup_WhenUserIsNotAdmin_ReturnsForbidden() Role = "DataAnalyst" }; - _userServiceMock.SignUpUser(Arg.Any()).Returns(Result.Ok(new CreateUserResponse())); + _userServiceMock.SignUp(Arg.Any()).Returns(Result.Ok(new CreateUserResponse())); _controller.ControllerContext = new ControllerContext { @@ -80,7 +82,7 @@ public async Task Signup_WhenRoleDoesNotExist_ReturnsBadRequest() }; _userServiceMock - .SignUpUser(Arg.Any()) + .SignUp(Arg.Any()) .Returns(Result.Fail("role does not exist")); _controller.ControllerContext = new ControllerContext @@ -132,7 +134,7 @@ public async Task Signup_WhenSignUpSucceeds_ReturnsOkResult() }; _userServiceMock - .SignUpUser(Arg.Any()) + .SignUp(Arg.Any()) .Returns(Result.Ok(createUserResponse)); _controller.ControllerContext = new ControllerContext @@ -153,7 +155,7 @@ public async Task Signup_WhenSignUpSucceeds_ReturnsOkResult() var okResult = Assert.IsType(result); Assert.Equal(200, okResult.StatusCode); - var responseValue = Assert.IsType(okResult.Value); + var responseValue = Assert.IsType(okResult.Value); Assert.Equal("Mobin", responseValue.FirstName); Assert.Equal("Barfi", responseValue.LastName); Assert.Equal("mobinbr99@gmail.com", responseValue.Email); @@ -187,7 +189,7 @@ public async Task Login_WhenLoginSucceeds_ReturnsOkResult() // Assert var okResult = Assert.IsType(result); - var response = Assert.IsType(okResult.Value); + var response = Assert.IsType(okResult.Value); Assert.Equal("MobinBarfi", response.UserName); Assert.Equal("FakeToken", response.Token); }