diff --git a/UserManagement.API/Controllers/AuthController.cs b/UserManagement.API/Controllers/AuthController.cs index 068d648..7f5d23e 100644 --- a/UserManagement.API/Controllers/AuthController.cs +++ b/UserManagement.API/Controllers/AuthController.cs @@ -32,14 +32,14 @@ namespace UserManagement.API.Controllers var user = await _userService.GetByUsernameAsync(login.Username, includeRoles: true); if (user is null) { - return Unauthorized(); + return Unauthorized("Benutzername und Passwort stimmen nicht überein!"); } // Validate login credentials var isValid = await _authService.ValidateAsync(login.Username, login.Password); if (!isValid) { - return Unauthorized(); + return Unauthorized("Benutzername und Passwort stimmen nicht überein!"); } // Create claims based on the user information diff --git a/UserManagement.API/Controllers/RoleController.cs b/UserManagement.API/Controllers/RoleController.cs index bc62d36..d9b01a0 100644 --- a/UserManagement.API/Controllers/RoleController.cs +++ b/UserManagement.API/Controllers/RoleController.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using UserManagement.Application.Dtos.Incomming; using UserManagement.Application.Interfaces; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; namespace UserManagement.API.Controllers { diff --git a/UserManagement.API/Controllers/UserController.cs b/UserManagement.API/Controllers/UserController.cs index 111fa90..5314c41 100644 --- a/UserManagement.API/Controllers/UserController.cs +++ b/UserManagement.API/Controllers/UserController.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using UserManagement.Application.Dtos.Incomming; using UserManagement.Application.Interfaces; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; namespace UserManagement.API.Controllers { @@ -14,9 +13,11 @@ namespace UserManagement.API.Controllers { // CTOR private readonly IUserService _userService; - public UserController(IUserService userService) + private readonly ILogger _logger; + public UserController(IUserService userService, ILogger logger) { _userService = userService; + _logger = logger; } // CREATE @@ -28,7 +29,6 @@ namespace UserManagement.API.Controllers [Authorize(Roles = "Admin")] public async Task CreateUser([FromBody] CreatingUserDto creatingUserDto) { - // Validate incomming model if (!ModelState.IsValid) { return BadRequest(ModelState); @@ -36,21 +36,15 @@ namespace UserManagement.API.Controllers try { - // Try to add user asynchronously - var result = await _userService.AddUserAsync(creatingUserDto); + var createdUser = await _userService.AddUserAsync(creatingUserDto); - // If user is successfully created, return a CreatedAtAction response with the created resource - if (result is not null) + if (createdUser is not null) { - var id = result.Id; - var createdResource = new { Id = id }; - var actionName = nameof(GetUserById); - var routeValue = new { id = createdResource.Id }; - return CreatedAtAction(actionName, routeValue, createdResource); + return CreatedAtAction(nameof(GetUserById), new { id = createdUser.Id }, createdUser); } else { - return BadRequest("Creation failed"); + return BadRequest("Erstellung des Benutzers fehlgeschlagen!"); } } catch (Exception ex) @@ -63,10 +57,18 @@ namespace UserManagement.API.Controllers [HttpGet] [SwaggerOperation(Summary = "Get all Users")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAllUsers([FromQuery] bool includeRoles = true) { - var users = await _userService.GetUsersAsync(includeRoles); - return Ok(users); + try + { + var users = await _userService.GetUsersAsync(includeRoles); + return Ok(users); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); + } } // READ BY ID @@ -77,16 +79,19 @@ namespace UserManagement.API.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetUserById(int id, [FromQuery] bool includeRoles = true) { - if (id <= 0) + try { - return BadRequest("Invalid Id"); + var user = await _userService.GetByIdAsync(id, includeRoles); + return Ok(user); } - var user = await _userService.GetByIdAsync(id, includeRoles); - if (user == null) + catch (ArgumentException ex) { - return NotFound(); + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); } - return Ok(user); } // READ BY USERNAME @@ -97,16 +102,19 @@ namespace UserManagement.API.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetUserByUsername(string username, [FromQuery] bool includeRoles = true) { - if (string.IsNullOrEmpty(username)) + try { - return BadRequest("Username connot be empty"); + var user = await _userService.GetByUsernameAsync(username, includeRoles); + return Ok(user); } - var user = await _userService.GetByUsernameAsync(username, includeRoles); - if (user == null) + catch (ArgumentException ex) { - return NotFound(); + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); } - return Ok(user); } // READ BY ROLE @@ -117,12 +125,19 @@ namespace UserManagement.API.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetUsersByRole(string role) { - if (string.IsNullOrEmpty(role)) + try { - return BadRequest("Role cannot be empty"); + var users = await _userService.GetByRoleAsync(role); + return Ok(users); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); } - var users = await _userService.GetByRoleAsync(role); - return Ok(users); } // UPDATE @@ -130,21 +145,27 @@ namespace UserManagement.API.Controllers [SwaggerOperation(Summary = "Update User")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task UpdateUser(int id, UpdatingUserDto updatingUserDto) { - if (id <= 0) + try { - return BadRequest("Invalid Id"); + var updated = await _userService.UpdateUserAsync(updatingUserDto); + return Ok(updated); } - - var updated = await _userService.UpdateUserAsync(updatingUserDto); - - if (!updated) + catch (ArgumentException ex) { - return BadRequest("Update failed"); + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError); } - - return Ok(updated); } // DELETE @@ -155,19 +176,19 @@ namespace UserManagement.API.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task DeleteUser([FromBody] int id) { - if (id <= 0) + try { - return BadRequest("Invalid Id"); + var deleted = await _userService.DeleteUserAsync(id); + return Ok(deleted); } - - var deleted = await _userService.DeleteUserAsync(id); - - if (!deleted) + catch (ArgumentException ex) { - return BadRequest("Deletion failed"); + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); } - - return Ok(); } } } diff --git a/UserManagement.API/Controllers/UserRolesController.cs b/UserManagement.API/Controllers/UserRolesController.cs new file mode 100644 index 0000000..cfb26ac --- /dev/null +++ b/UserManagement.API/Controllers/UserRolesController.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using UserManagement.Application.Dtos.Incomming; +using UserManagement.Application.Dtos.Outgoing; +using UserManagement.Application.Interfaces; + +namespace UserManagement.API.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class UserRolesController : Controller + { + // CTOR + private readonly IUserRolesService _userRolesService; + public UserRolesController(IUserRolesService userRolesService) + { + _userRolesService = userRolesService; + } + + // CREATE ASSIGNMENT + [HttpPost("AssignRoleToUser")] + [SwaggerOperation(Summary = "Assign Role to User the better way")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task CreateAssignmentAsync(CreatingUserRolesDto creatingUserRolesDto) + { + try + { + var result = await _userRolesService.CreateAssignmentAsync(creatingUserRolesDto); + + if (result) + { + return Ok("Rolle erfolgreich zugewiesen!"); + } + else + { + return BadRequest("Zuweisen der Rolle fehlgeschlagen!"); + } + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); + } + } + + // REMOVE ROLE FROM USER + [HttpDelete("RemoveRoleFromUser")] + [SwaggerOperation(Summary = "Remove Role from User")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task RemoveRoleFromUser(CreatingUserRolesDto creatingUserRolesDto) + { + try + { + var result = await _userRolesService.RemoveRoleFromUserAsync(creatingUserRolesDto); + + if (result) + { + return Ok("Rolle erfolgreich entfernt!"); + } + else + { + return BadRequest("Entfernung der Rolle fehlgeschlagen!"); + } + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + catch (KeyNotFoundException ex) + { + return NotFound(ex.Message); + } + } + } +} diff --git a/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.Designer.cs b/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.Designer.cs new file mode 100644 index 0000000..cf2930c --- /dev/null +++ b/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.Designer.cs @@ -0,0 +1,146 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using UserManagement.Infrastructure; + +#nullable disable + +namespace UserManagement.API.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240926093702_AddIdColumnToUserRoles")] + partial class AddIdColumnToUserRoles + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("UserManagement.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("ID"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime") + .HasColumnName("CREATION_DATE"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasColumnName("ROLE"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("UserManagement.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("ID"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("FIRST_NAME"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("LAST_NAME"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("PASSWORD_HASH"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasColumnName("USER_NAME"); + + b.HasKey("Id"); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("UserManagement.Domain.Entities.UserRoles", b => + { + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("USER_ID"); + + b.Property("RoleId") + .HasColumnType("int") + .HasColumnName("ROLE_ID"); + + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("ID"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles"); + }); + + modelBuilder.Entity("UserManagement.Domain.Entities.UserRoles", b => + { + b.HasOne("UserManagement.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("UserManagement.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("UserManagement.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("UserManagement.Domain.Entities.User", b => + { + b.Navigation("UserRoles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.cs b/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.cs new file mode 100644 index 0000000..4315f8f --- /dev/null +++ b/UserManagement.API/Migrations/20240926093702_AddIdColumnToUserRoles.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace UserManagement.API.Migrations +{ + /// + public partial class AddIdColumnToUserRoles : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ID", + table: "UserRoles", + type: "int", + nullable: false, + defaultValue: 0) + .Annotation("SqlServer:Identity", "1, 1"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ID", + table: "UserRoles"); + } + } +} diff --git a/UserManagement.API/Migrations/ApplicationDbContextModelSnapshot.cs b/UserManagement.API/Migrations/ApplicationDbContextModelSnapshot.cs index 5577e8c..da9bcdc 100644 --- a/UserManagement.API/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/UserManagement.API/Migrations/ApplicationDbContextModelSnapshot.cs @@ -85,7 +85,7 @@ namespace UserManagement.API.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("UserManagement.Domain.Entities.UserRole", b => + modelBuilder.Entity("UserManagement.Domain.Entities.UserRoles", b => { b.Property("UserId") .HasColumnType("int") @@ -95,6 +95,13 @@ namespace UserManagement.API.Migrations .HasColumnType("int") .HasColumnName("ROLE_ID"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("ID"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + b.HasKey("UserId", "RoleId"); b.HasIndex("RoleId"); @@ -102,7 +109,7 @@ namespace UserManagement.API.Migrations b.ToTable("UserRoles"); }); - modelBuilder.Entity("UserManagement.Domain.Entities.UserRole", b => + modelBuilder.Entity("UserManagement.Domain.Entities.UserRoles", b => { b.HasOne("UserManagement.Domain.Entities.Role", "Role") .WithMany("UserRoles") diff --git a/UserManagement.API/Program.cs b/UserManagement.API/Program.cs index d9be6be..331fbe3 100644 --- a/UserManagement.API/Program.cs +++ b/UserManagement.API/Program.cs @@ -18,11 +18,14 @@ builder.Services.AddSwaggerGen(s => s.EnableAnnotations()); builder.Services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/UserManagement.API/user-by-role.sql b/UserManagement.API/user-by-role.sql new file mode 100644 index 0000000..4f76778 --- /dev/null +++ b/UserManagement.API/user-by-role.sql @@ -0,0 +1,15 @@ +DECLARE @__role_0 nvarchar(450) = N'admin'; + +SELECT [u].[ID], [u].[FIRST_NAME], [u].[LAST_NAME], [u].[PASSWORD_HASH], [u].[USER_NAME], [t].[USER_ID], [t].[ROLE_ID], [t].[ID], [t].[CREATION_DATE], [t].[ROLE] +FROM [Users] AS [u] +LEFT JOIN ( + SELECT [u1].[USER_ID], [u1].[ROLE_ID], [r0].[ID], [r0].[CREATION_DATE], [r0].[ROLE] + FROM [UserRoles] AS [u1] + INNER JOIN [Roles] AS [r0] ON [u1].[ROLE_ID] = [r0].[ID] +) AS [t] ON [u].[ID] = [t].[USER_ID] +WHERE EXISTS ( + SELECT 1 + FROM [UserRoles] AS [u0] + INNER JOIN [Roles] AS [r] ON [u0].[ROLE_ID] = [r].[ID] + WHERE [u].[ID] = [u0].[USER_ID] AND [r].[ROLE] = @__role_0) +ORDER BY [u].[ID], [t].[USER_ID], [t].[ROLE_ID] \ No newline at end of file diff --git a/UserManagement.App/Dtos/Incomming/CreatingUserDto.cs b/UserManagement.App/Dtos/Incomming/CreatingUserDto.cs index dd9ab57..f5e40e3 100644 --- a/UserManagement.App/Dtos/Incomming/CreatingUserDto.cs +++ b/UserManagement.App/Dtos/Incomming/CreatingUserDto.cs @@ -1,6 +1,4 @@ -using UserManagement.Application.Dtos.Outgoing; - -namespace UserManagement.Application.Dtos.Incomming +namespace UserManagement.Application.Dtos.Incomming { public class CreatingUserDto { @@ -11,7 +9,5 @@ namespace UserManagement.Application.Dtos.Incomming public string LastName { get; set; } public string Password { get; set; } - - public ICollection RoleIds { get; set; } } } diff --git a/UserManagement.App/Dtos/Incomming/CreatingUserRolesDto.cs b/UserManagement.App/Dtos/Incomming/CreatingUserRolesDto.cs new file mode 100644 index 0000000..a53fbd2 --- /dev/null +++ b/UserManagement.App/Dtos/Incomming/CreatingUserRolesDto.cs @@ -0,0 +1,8 @@ +namespace UserManagement.Application.Dtos.Incomming +{ + public class CreatingUserRolesDto + { + public int UserId { get; set; } + public int RoleId { get; set; } + } +} diff --git a/UserManagement.App/Dtos/Incomming/UpdatingUserDto.cs b/UserManagement.App/Dtos/Incomming/UpdatingUserDto.cs index ec6aa2e..13d5ab6 100644 --- a/UserManagement.App/Dtos/Incomming/UpdatingUserDto.cs +++ b/UserManagement.App/Dtos/Incomming/UpdatingUserDto.cs @@ -1,4 +1,6 @@ -namespace UserManagement.Application.Dtos.Incomming +using UserManagement.Domain.Entities; + +namespace UserManagement.Application.Dtos.Incomming { public class UpdatingUserDto { @@ -11,7 +13,5 @@ public string LastName { get; set; } public string Password { get; set; } - - public ICollection RoleIds { get; set; } } } diff --git a/UserManagement.App/Dtos/Incomming/UpdatingUserRolesDto.cs b/UserManagement.App/Dtos/Incomming/UpdatingUserRolesDto.cs new file mode 100644 index 0000000..954565d --- /dev/null +++ b/UserManagement.App/Dtos/Incomming/UpdatingUserRolesDto.cs @@ -0,0 +1,8 @@ +namespace UserManagement.Application.Dtos.Incomming +{ + public class UpdatingUserRolesDto + { + public int UserId { get; set; } + public int RoleId { get; set; } + } +} diff --git a/UserManagement.App/Dtos/Outgoing/ReadingUserRolesDto.cs b/UserManagement.App/Dtos/Outgoing/ReadingUserRolesDto.cs index 0bd3541..35bd099 100644 --- a/UserManagement.App/Dtos/Outgoing/ReadingUserRolesDto.cs +++ b/UserManagement.App/Dtos/Outgoing/ReadingUserRolesDto.cs @@ -2,6 +2,10 @@ { public class ReadingUserRolesDto { + public int UserId { get; set; } + + public string UserName { get; set; } + public int RoleId { get; set; } public string RoleName { get; set; } diff --git a/UserManagement.App/Interfaces/IUserRolesService.cs b/UserManagement.App/Interfaces/IUserRolesService.cs new file mode 100644 index 0000000..707f6cf --- /dev/null +++ b/UserManagement.App/Interfaces/IUserRolesService.cs @@ -0,0 +1,14 @@ +using UserManagement.Application.Dtos.Incomming; +using UserManagement.Application.Dtos.Outgoing; + +namespace UserManagement.Application.Interfaces +{ + public interface IUserRolesService + { + // CREATE ASSIGNMENT + Task CreateAssignmentAsync(CreatingUserRolesDto creatingUserRolesDto); + + // REMOVE ROLE FORM USER + Task RemoveRoleFromUserAsync(CreatingUserRolesDto creatingUserRolesDto); + } +} diff --git a/UserManagement.App/MappingProfiles/BasicDtoMappingProfile.cs b/UserManagement.App/MappingProfiles/BasicDtoMappingProfile.cs index 35a0068..f56548b 100644 --- a/UserManagement.App/MappingProfiles/BasicDtoMappingProfile.cs +++ b/UserManagement.App/MappingProfiles/BasicDtoMappingProfile.cs @@ -11,23 +11,18 @@ namespace UserManagement.Application.MappingProfiles { // ROLE CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); + // USER ROLES + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + // USER CreateMap() - .ForMember(dest => dest.RoleIds, opt => opt.Ignore()) .ReverseMap() .AfterMap((src, dest) => { - dest.UserRoles = src.RoleIds?.Select(roleId => new UserRole - { - RoleId = roleId, - User = dest - }).ToList(); - dest.PasswordHash = BCrypt.Net.BCrypt.HashPassword(src.Password); }); @@ -40,16 +35,9 @@ namespace UserManagement.Application.MappingProfiles }).ToList())); CreateMap() - .ForMember(dest => dest.RoleIds, opt => opt.Ignore()) .ReverseMap() .AfterMap((src, dest) => { - dest.UserRoles = src.RoleIds?.Select(roleId => new UserRole - { - RoleId = roleId, - UserId = dest.Id - }).ToList(); - dest.PasswordHash = BCrypt.Net.BCrypt.HashPassword(src.Password); }); } diff --git a/UserManagement.App/Services/AuthService.cs b/UserManagement.App/Services/AuthService.cs index 2de0791..55fb7ff 100644 --- a/UserManagement.App/Services/AuthService.cs +++ b/UserManagement.App/Services/AuthService.cs @@ -6,7 +6,7 @@ namespace UserManagement.Application.Services public class AuthService : IAuthService { // CTOR - private IUserRepository _userRepository; + private readonly IUserRepository _userRepository; public AuthService(IUserRepository userRepository) { _userRepository = userRepository; diff --git a/UserManagement.App/Services/UserRolesService.cs b/UserManagement.App/Services/UserRolesService.cs new file mode 100644 index 0000000..a288376 --- /dev/null +++ b/UserManagement.App/Services/UserRolesService.cs @@ -0,0 +1,119 @@ +using AutoMapper; +using UserManagement.Application.Dtos.Incomming; +using UserManagement.Application.Dtos.Outgoing; +using UserManagement.Application.Interfaces; +using UserManagement.Domain.Entities; +using UserManagement.Infrastructure.Interfaces; + +namespace UserManagement.Application.Services +{ + public class UserRolesService : IUserRolesService + { + // CTOR + private readonly IUserRolesRepository _userRolesRepository; + private readonly IUserRepository _userRepository; + private readonly IRoleRepository _roleRepository; + private readonly IMapper _mapper; + + public UserRolesService(IUserRolesRepository userRolesRepository, IUserRepository userRepository, IRoleRepository roleRepository, IMapper mapper) + { + _userRolesRepository = userRolesRepository; + _userRepository = userRepository; + _roleRepository = roleRepository; + _mapper = mapper; + } + + // CREATE ASSIGNMENT + public async Task CreateAssignmentAsync(CreatingUserRolesDto creatingUserRolesDto) + { + if (creatingUserRolesDto.UserId <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + + var user = await _userRepository.GetByIdAsync(creatingUserRolesDto.UserId); + + if (user == null) + { + throw new KeyNotFoundException("Benutzer nicht gefunden!"); + } + + if (creatingUserRolesDto.RoleId <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + + var role = await _roleRepository.GetByIdAsync(creatingUserRolesDto.RoleId); + + if (role == null) + { + throw new KeyNotFoundException("Rolle nicht gefunden!"); + } + + var userRoles = _mapper.Map(creatingUserRolesDto); + + var assigned = await _userRolesRepository.CreateAssignmentAsync(userRoles); + + return assigned; + } + + //// ASSIGNING ROLES BY NAMES + //public async Task AssignAsync(string username, string roleName) + //{ + // var user = await _userRepository.GetByUsernameAsync(username); + // var role = await _roleRepository.GetByNameAsync(roleName); + + // if (user is null || role is null) + // { + // return false; + // } + + // var dto = new CreatingUserRolesDto() + // { + // RoleId = role.Id, + // UserId = user.Id + // }; + + // return await CreateAssignmentAsync(dto); + //} + + // REMOVE ROLE FROM USER + public async Task RemoveRoleFromUserAsync(CreatingUserRolesDto creatingUserRolesDto) + { + if (creatingUserRolesDto.UserId <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + + var user = await _userRepository.GetByIdAsync(creatingUserRolesDto.UserId); + + if (user == null) + { + throw new KeyNotFoundException("Benutzer nicht gefunden!"); + } + + if (creatingUserRolesDto.RoleId <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + + var role = await _roleRepository.GetByIdAsync(creatingUserRolesDto.RoleId); + + if (role == null) + { + throw new KeyNotFoundException("Rolle nicht gefunden!"); + } + + var userRole = _mapper.Map(creatingUserRolesDto); + + if (userRole == null) + { + throw new KeyNotFoundException("Die Rolle ist dem Benutzer nicht zugewiesen!"); + } + + var result = await _userRolesRepository.DeleteAssignmentAsync(userRole); + + return result; + } + } +} diff --git a/UserManagement.App/Services/UserService.cs b/UserManagement.App/Services/UserService.cs index 8f65282..1440691 100644 --- a/UserManagement.App/Services/UserService.cs +++ b/UserManagement.App/Services/UserService.cs @@ -30,22 +30,8 @@ namespace UserManagement.Application.Services user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(creatingUserDto.Password); } - user.UserRoles = new List(); - - foreach (var roleId in creatingUserDto.RoleIds) - { - var role = await _roleRepository.GetByIdAsync(roleId); - if (role is not null) - { - user.UserRoles.Add(new UserRole { UserId = user.Id, RoleId = role.Id }); - } - else - { - throw new ArgumentException($"Role with Id {roleId} not found"); - } - } - var created = await _userRepository.AddAsync(user); + return created; } @@ -53,42 +39,90 @@ namespace UserManagement.Application.Services public async Task> GetUsersAsync(bool includeRoles = true) { var users = await _userRepository.GetAllAsync(includeRoles); + + if (users == null) + { + throw new KeyNotFoundException("Keine Benutzer gefunden!"); + } + var readDto = _mapper.Map>(users); + return readDto; } // READ BY ID public async Task GetByIdAsync(int id, bool includeRoles = true) { + if (id <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + var user = await _userRepository.GetByIdAsync(id, includeRoles); + + if (user == null) + { + throw new KeyNotFoundException("Benutzer nicht gefunden!"); + } + var readDto = _mapper.Map(user); + return readDto; } // READ BY USERNAME public async Task GetByUsernameAsync(string username, bool includeRoles = true) { + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentException("Ungültiger Benutzername!"); + } + var user = await _userRepository.GetByUsernameAsync(username, includeRoles); + + if (user == null) + { + throw new KeyNotFoundException("Benutzer nicht gefunden!"); + } + var readDto = _mapper.Map(user); + return readDto; } // READ BY ROLE public async Task> GetByRoleAsync(string role) { + if (string.IsNullOrEmpty(role)) + { + throw new ArgumentException("Ungültige Rolle!"); + } + var users = await _userRepository.GetByRoleAsync(role); + + if (users == null) + { + throw new KeyNotFoundException("Keine Benutzer in dieser Rolle gefunden!"); + } + var readDto = _mapper.Map>(users); + return readDto; } // UPDATE public async Task UpdateUserAsync(UpdatingUserDto updatingUserDto) { + if (updatingUserDto.Id <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } + var user = await _userRepository.GetByIdAsync(updatingUserDto.Id); - if (user is null) + if (user == null) { - return false; + throw new KeyNotFoundException("Benutzer nicht gefunden!"); } _mapper.Map(updatingUserDto, user); @@ -98,34 +132,28 @@ namespace UserManagement.Application.Services user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(updatingUserDto.Password); } - user.UserRoles!.Clear(); - - foreach(var roleId in updatingUserDto.RoleIds) - { - var role = await _roleRepository.GetByIdAsync(roleId); - if (role is not null) - { - user.UserRoles.Add(new UserRole { UserId = user.Id, RoleId = role.Id }); - } - else - { - throw new ArgumentException($"Role with Id {roleId} not found"); - } - } - bool isUpdated = await _userRepository.UpdateAsync(user); + return isUpdated; } // DELETE public async Task DeleteUserAsync(int id) { - User? user = await _userRepository.GetByIdAsync(id); + if (id <= 0) + { + throw new ArgumentException("Ungültige Id!"); + } - if (user is null) - return false; + var user = await _userRepository.GetByIdAsync(id); + + if (user == null) + { + throw new KeyNotFoundException("Benutzer nicht gefunden!"); + } bool isDeleted = await _userRepository.DeleteAsync(user); + return isDeleted; } } diff --git a/UserManagement.Domain/Entities/Role.cs b/UserManagement.Domain/Entities/Role.cs index 7d6d630..d0c6434 100644 --- a/UserManagement.Domain/Entities/Role.cs +++ b/UserManagement.Domain/Entities/Role.cs @@ -1,5 +1,4 @@ -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace UserManagement.Domain.Entities @@ -16,7 +15,7 @@ namespace UserManagement.Domain.Entities public required string Name { get; set; } [Column("USERS")] - public ICollection? UserRoles { get; set; } = new Collection(); + public ICollection? UserRoles { get; set; } [Required] [Column("CREATION_DATE", TypeName = "datetime")] diff --git a/UserManagement.Domain/Entities/User.cs b/UserManagement.Domain/Entities/User.cs index 443ddaf..7ef3981 100644 --- a/UserManagement.Domain/Entities/User.cs +++ b/UserManagement.Domain/Entities/User.cs @@ -1,5 +1,4 @@ -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace UserManagement.Domain.Entities @@ -27,6 +26,6 @@ namespace UserManagement.Domain.Entities [Column("PASSWORD_HASH")] public required string PasswordHash { get; set; } - public ICollection? UserRoles { get; set; } = new Collection(); + public ICollection? UserRoles { get; set; } } -} \ No newline at end of file +} diff --git a/UserManagement.Domain/Entities/UserRole.cs b/UserManagement.Domain/Entities/UserRoles.cs similarity index 56% rename from UserManagement.Domain/Entities/UserRole.cs rename to UserManagement.Domain/Entities/UserRoles.cs index 5d0eac4..29bf13a 100644 --- a/UserManagement.Domain/Entities/UserRole.cs +++ b/UserManagement.Domain/Entities/UserRoles.cs @@ -1,9 +1,15 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace UserManagement.Domain.Entities { - public class UserRole + public class UserRoles { + [Column("ID")] + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + [Column("USER_ID")] public int UserId { get; set; } diff --git a/UserManagement.Infrastructure/ApplicationDbContext.cs b/UserManagement.Infrastructure/ApplicationDbContext.cs index 012e70b..8032f38 100644 --- a/UserManagement.Infrastructure/ApplicationDbContext.cs +++ b/UserManagement.Infrastructure/ApplicationDbContext.cs @@ -12,7 +12,7 @@ namespace UserManagement.Infrastructure public DbSet Users { get; set; } public DbSet Roles { get; set; } - public DbSet UserRoles { get; set; } + public DbSet UserRoles { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -26,15 +26,15 @@ namespace UserManagement.Infrastructure .HasIndex(r => r.Name) .IsUnique(); - modelBuilder.Entity() + modelBuilder.Entity() .HasKey(ur => new { ur.UserId, ur.RoleId }); - modelBuilder.Entity() + modelBuilder.Entity() .HasOne(ur => ur.User) .WithMany(u => u.UserRoles) .HasForeignKey(ur => ur.UserId); - modelBuilder.Entity() + modelBuilder.Entity() .HasOne(ur => ur.Role) .WithMany(r => r.UserRoles) .HasForeignKey(ur => ur.RoleId); diff --git a/UserManagement.Infrastructure/Interfaces/IUserRolesRepository.cs b/UserManagement.Infrastructure/Interfaces/IUserRolesRepository.cs new file mode 100644 index 0000000..98f1c16 --- /dev/null +++ b/UserManagement.Infrastructure/Interfaces/IUserRolesRepository.cs @@ -0,0 +1,13 @@ +using UserManagement.Domain.Entities; + +namespace UserManagement.Infrastructure.Interfaces +{ + public interface IUserRolesRepository + { + // CREATE ASSIGNMENT + Task CreateAssignmentAsync(UserRoles userRoles); + + // DELETE ASSIGNMENT + Task DeleteAssignmentAsync(UserRoles userRoles); + } +} diff --git a/UserManagement.Infrastructure/Repositories/UserRepository.cs b/UserManagement.Infrastructure/Repositories/UserRepository.cs index d3ad916..4307617 100644 --- a/UserManagement.Infrastructure/Repositories/UserRepository.cs +++ b/UserManagement.Infrastructure/Repositories/UserRepository.cs @@ -63,11 +63,14 @@ namespace UserManagement.Infrastructure.Repositories // READ BY ROLE public async Task> GetByRoleAsync(string role) { - return await _context.Users + var query = _context.Users .Include(u => u.UserRoles)! .ThenInclude(ur => ur.Role) - .Where(ur => ur.UserRoles!.Any(r => r.Role!.Name == role)) - .ToListAsync(); + .Where(ur => ur.UserRoles!.Any(r => r.Role!.Name == role)); + + var sql = query.ToQueryString(); + + return await query.ToListAsync(); } // UPDATE diff --git a/UserManagement.Infrastructure/Repositories/UserRolesRepository.cs b/UserManagement.Infrastructure/Repositories/UserRolesRepository.cs new file mode 100644 index 0000000..0ff2f73 --- /dev/null +++ b/UserManagement.Infrastructure/Repositories/UserRolesRepository.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using UserManagement.Domain.Entities; +using UserManagement.Infrastructure.Interfaces; + +namespace UserManagement.Infrastructure.Repositories +{ + public class UserRolesRepository : IUserRolesRepository + { + // CTOR + private readonly ApplicationDbContext _context; + public UserRolesRepository(ApplicationDbContext context) + { + _context = context; + } + + // CREATE ASSIGNMENT + public async Task CreateAssignmentAsync(UserRoles userRoles) + { + await _context.UserRoles.AddAsync(userRoles); + var results = await _context.SaveChangesAsync(); + return results > 0; + } + + // DELETE ASSIGNMENT + public async Task DeleteAssignmentAsync(UserRoles userRoles) + { + _context.UserRoles.Remove(userRoles); + var result = await _context.SaveChangesAsync(); + return result > 0; + } + } +}