Add user and role management features
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Distribution.Features.Role;
|
||||||
|
|
||||||
|
public interface IRoleRepository
|
||||||
|
{
|
||||||
|
Task<Models.Role?> GetByIdAsync(Guid id);
|
||||||
|
Task<List<Models.Role>> GetAllAsync();
|
||||||
|
Task<List<Models.RolePermission>> GetPermissionsAsync(Guid roleId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Distribution.Features.Role;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/roles")]
|
||||||
|
[Authorize]
|
||||||
|
public class RoleController(RoleService service) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAll() =>
|
||||||
|
Ok(await service.GetAllAsync());
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}")]
|
||||||
|
public async Task<IActionResult> GetById(Guid id)
|
||||||
|
{
|
||||||
|
var result = await service.GetByIdAsync(id);
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}/permissions")]
|
||||||
|
public async Task<IActionResult> GetPermissions(Guid id)
|
||||||
|
{
|
||||||
|
var result = await service.GetPermissionsAsync(id);
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Distribution.Features.Role;
|
||||||
|
|
||||||
|
public record RoleResponse(Guid Id, string Name, int Level, bool IsActive, DateTime CreatedAt);
|
||||||
|
|
||||||
|
public record RolePermissionResponse(Guid PermissionId, string Code, string Description);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Distribution.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Distribution.Features.Role;
|
||||||
|
|
||||||
|
public class RoleRepository(AppDBContext context) : IRoleRepository
|
||||||
|
{
|
||||||
|
public async Task<Models.Role?> GetByIdAsync(Guid id) =>
|
||||||
|
await context.Roles.FindAsync(id);
|
||||||
|
|
||||||
|
public async Task<List<Models.Role>> GetAllAsync() =>
|
||||||
|
await context.Roles.ToListAsync();
|
||||||
|
|
||||||
|
public async Task<List<Models.RolePermission>> GetPermissionsAsync(Guid roleId) =>
|
||||||
|
await context.RolePermissions
|
||||||
|
.Include(rp => rp.Permission)
|
||||||
|
.Where(rp => rp.FK_RoleId == roleId && rp.IsActive)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Distribution.Features.Role;
|
||||||
|
|
||||||
|
public class RoleService(IRoleRepository repo)
|
||||||
|
{
|
||||||
|
public async Task<List<RoleResponse>> GetAllAsync() =>
|
||||||
|
(await repo.GetAllAsync())
|
||||||
|
.Where(r => r.IsActive)
|
||||||
|
.Select(r => new RoleResponse(r.Id, r.Name, r.Level, r.IsActive, r.CreatedAt))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
public async Task<RoleResponse?> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
var role = await repo.GetByIdAsync(id);
|
||||||
|
if (role is null || !role.IsActive) return null;
|
||||||
|
return new RoleResponse(role.Id, role.Name, role.Level, role.IsActive, role.CreatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<RolePermissionResponse>?> GetPermissionsAsync(Guid roleId)
|
||||||
|
{
|
||||||
|
var role = await repo.GetByIdAsync(roleId);
|
||||||
|
if (role is null || !role.IsActive) return null;
|
||||||
|
return (await repo.GetPermissionsAsync(roleId))
|
||||||
|
.Select(rp => new RolePermissionResponse(rp.FK_PermissionId, rp.Permission.Code, rp.Permission.Description))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Distribution.Features.User;
|
||||||
|
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
Task<Models.User?> GetByIdAsync(Guid id);
|
||||||
|
Task<Models.User?> GetByUsernameAsync(string username);
|
||||||
|
Task<List<Models.User>> GetAllAsync();
|
||||||
|
Task AddAsync(Models.User entity);
|
||||||
|
void Update(Models.User entity);
|
||||||
|
void Remove(Models.User entity);
|
||||||
|
|
||||||
|
Task<List<Models.UserRegionScope>> GetRegionScopesAsync(Guid userId);
|
||||||
|
Task<List<Models.UserRouteScope>> GetRouteScopesAsync(Guid userId);
|
||||||
|
Task<List<Models.UserStoreScope>> GetStoreScopesAsync(Guid userId);
|
||||||
|
Task ReplaceRegionScopesAsync(Guid userId, IEnumerable<Guid> regionIds);
|
||||||
|
Task ReplaceRouteScopesAsync(Guid userId, IEnumerable<Guid> routeIds);
|
||||||
|
Task ReplaceStoreScopesAsync(Guid userId, IEnumerable<Guid> storeIds);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Distribution.Features.User;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/users")]
|
||||||
|
[Authorize]
|
||||||
|
public class UserController(UserService service) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "users.view")]
|
||||||
|
public async Task<IActionResult> GetAll() =>
|
||||||
|
Ok(await service.GetAllAsync());
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}")]
|
||||||
|
[Authorize(Policy = "users.view")]
|
||||||
|
public async Task<IActionResult> GetById(Guid id)
|
||||||
|
{
|
||||||
|
var result = await service.GetByIdAsync(id);
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "users.create")]
|
||||||
|
public async Task<IActionResult> Create(CreateUserRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await service.CreateAsync(request, ct);
|
||||||
|
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id:guid}")]
|
||||||
|
[Authorize(Policy = "users.update")]
|
||||||
|
public async Task<IActionResult> Update(Guid id, UpdateUserRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await service.UpdateAsync(id, request, ct);
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id:guid}/role")]
|
||||||
|
[Authorize(Policy = "users.assign_role")]
|
||||||
|
public async Task<IActionResult> AssignRole(Guid id, AssignUserRoleRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await service.AssignRoleAsync(id, request, ct);
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id:guid}/scope")]
|
||||||
|
[Authorize(Policy = "users.assign_scope")]
|
||||||
|
public async Task<IActionResult> AssignScope(Guid id, AssignUserScopeRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var updated = await service.AssignScopeAsync(id, request, ct);
|
||||||
|
return updated ? NoContent() : NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:guid}")]
|
||||||
|
[Authorize(Policy = "users.deactivate")]
|
||||||
|
public async Task<IActionResult> Deactivate(Guid id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var deactivated = await service.DeactivateAsync(id, ct);
|
||||||
|
return deactivated ? NoContent() : NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Distribution.Features.User;
|
||||||
|
|
||||||
|
public record CreateUserRequest(
|
||||||
|
string Username,
|
||||||
|
string Password,
|
||||||
|
string FullName,
|
||||||
|
Guid RoleId,
|
||||||
|
Guid? ReportsToUserId);
|
||||||
|
|
||||||
|
public record UpdateUserRequest(string FullName);
|
||||||
|
|
||||||
|
public record AssignUserRoleRequest(Guid RoleId);
|
||||||
|
|
||||||
|
public record AssignUserScopeRequest(
|
||||||
|
List<Guid> RegionIds,
|
||||||
|
List<Guid> RouteIds,
|
||||||
|
List<Guid> StoreIds);
|
||||||
|
|
||||||
|
public record UserResponse(
|
||||||
|
Guid Id,
|
||||||
|
string Username,
|
||||||
|
string FullName,
|
||||||
|
Guid RoleId,
|
||||||
|
string RoleName,
|
||||||
|
Guid? ReportsToUserId,
|
||||||
|
bool IsActive,
|
||||||
|
DateTime CreatedAt,
|
||||||
|
DateTime? UpdatedAt);
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using Distribution.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Distribution.Features.User;
|
||||||
|
|
||||||
|
public class UserRepository(AppDBContext context) : IUserRepository
|
||||||
|
{
|
||||||
|
public async Task<Models.User?> GetByIdAsync(Guid id) =>
|
||||||
|
await context.Users.Include(u => u.Role).FirstOrDefaultAsync(u => u.Id == id);
|
||||||
|
|
||||||
|
public async Task<Models.User?> GetByUsernameAsync(string username) =>
|
||||||
|
await context.Users.Include(u => u.Role).FirstOrDefaultAsync(u => u.Username == username);
|
||||||
|
|
||||||
|
public async Task<List<Models.User>> GetAllAsync() =>
|
||||||
|
await context.Users.Include(u => u.Role).ToListAsync();
|
||||||
|
|
||||||
|
public async Task AddAsync(Models.User entity) =>
|
||||||
|
await context.Users.AddAsync(entity);
|
||||||
|
|
||||||
|
public void Update(Models.User entity) =>
|
||||||
|
context.Users.Update(entity);
|
||||||
|
|
||||||
|
public void Remove(Models.User entity)
|
||||||
|
{
|
||||||
|
entity.IsActive = false;
|
||||||
|
context.Users.Update(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Models.UserRegionScope>> GetRegionScopesAsync(Guid userId) =>
|
||||||
|
await context.UserRegionScopes.Where(s => s.FK_UserId == userId && s.IsActive).ToListAsync();
|
||||||
|
|
||||||
|
public async Task<List<Models.UserRouteScope>> GetRouteScopesAsync(Guid userId) =>
|
||||||
|
await context.UserRouteScopes.Where(s => s.FK_UserId == userId && s.IsActive).ToListAsync();
|
||||||
|
|
||||||
|
public async Task<List<Models.UserStoreScope>> GetStoreScopesAsync(Guid userId) =>
|
||||||
|
await context.UserStoreScopes.Where(s => s.FK_UserId == userId && s.IsActive).ToListAsync();
|
||||||
|
|
||||||
|
public async Task ReplaceRegionScopesAsync(Guid userId, IEnumerable<Guid> regionIds)
|
||||||
|
{
|
||||||
|
var existing = await context.UserRegionScopes.Where(s => s.FK_UserId == userId).ToListAsync();
|
||||||
|
context.UserRegionScopes.RemoveRange(existing);
|
||||||
|
await context.UserRegionScopes.AddRangeAsync(
|
||||||
|
regionIds.Select(id => new Models.UserRegionScope { FK_UserId = userId, FK_RegionId = id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplaceRouteScopesAsync(Guid userId, IEnumerable<Guid> routeIds)
|
||||||
|
{
|
||||||
|
var existing = await context.UserRouteScopes.Where(s => s.FK_UserId == userId).ToListAsync();
|
||||||
|
context.UserRouteScopes.RemoveRange(existing);
|
||||||
|
await context.UserRouteScopes.AddRangeAsync(
|
||||||
|
routeIds.Select(id => new Models.UserRouteScope { FK_UserId = userId, FK_RouteId = id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplaceStoreScopesAsync(Guid userId, IEnumerable<Guid> storeIds)
|
||||||
|
{
|
||||||
|
var existing = await context.UserStoreScopes.Where(s => s.FK_UserId == userId).ToListAsync();
|
||||||
|
context.UserStoreScopes.RemoveRange(existing);
|
||||||
|
await context.UserStoreScopes.AddRangeAsync(
|
||||||
|
storeIds.Select(id => new Models.UserStoreScope { FK_UserId = userId, FK_StoreId = id }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using Distribution.Infra.UoW;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Distribution.Features.User;
|
||||||
|
|
||||||
|
public class UserService(IUserRepository repo, IUnitOfWork uow)
|
||||||
|
{
|
||||||
|
private readonly PasswordHasher<Models.User> _hasher = new();
|
||||||
|
|
||||||
|
private static UserResponse ToResponse(Models.User u) =>
|
||||||
|
new(u.Id, u.Username, u.FullName, u.FK_RoleId, u.Role?.Name ?? string.Empty,
|
||||||
|
u.FK_ReportsToUserId, u.IsActive, u.CreatedAt, u.UpdatedAt);
|
||||||
|
|
||||||
|
public async Task<List<UserResponse>> GetAllAsync() =>
|
||||||
|
(await repo.GetAllAsync())
|
||||||
|
.Where(u => u.IsActive)
|
||||||
|
.Select(ToResponse)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
public async Task<UserResponse?> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
var user = await repo.GetByIdAsync(id);
|
||||||
|
if (user is null || !user.IsActive) return null;
|
||||||
|
return ToResponse(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserResponse> CreateAsync(CreateUserRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var user = new Models.User
|
||||||
|
{
|
||||||
|
Username = request.Username,
|
||||||
|
FullName = request.FullName,
|
||||||
|
FK_RoleId = request.RoleId,
|
||||||
|
FK_ReportsToUserId = request.ReportsToUserId
|
||||||
|
};
|
||||||
|
user.PasswordHash = _hasher.HashPassword(user, request.Password);
|
||||||
|
|
||||||
|
await uow.BeginAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await repo.AddAsync(user);
|
||||||
|
await uow.CommitAsync(ct);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return ToResponse(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserResponse?> UpdateAsync(Guid id, UpdateUserRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var user = await repo.GetByIdAsync(id);
|
||||||
|
if (user is null || !user.IsActive) return null;
|
||||||
|
user.FullName = request.FullName;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
await uow.BeginAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
repo.Update(user);
|
||||||
|
await uow.CommitAsync(ct);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return ToResponse(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserResponse?> AssignRoleAsync(Guid id, AssignUserRoleRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var user = await repo.GetByIdAsync(id);
|
||||||
|
if (user is null || !user.IsActive) return null;
|
||||||
|
user.FK_RoleId = request.RoleId;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
await uow.BeginAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
repo.Update(user);
|
||||||
|
await uow.CommitAsync(ct);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return ToResponse(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AssignScopeAsync(Guid id, AssignUserScopeRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var user = await repo.GetByIdAsync(id);
|
||||||
|
if (user is null || !user.IsActive) return false;
|
||||||
|
|
||||||
|
await uow.BeginAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await repo.ReplaceRegionScopesAsync(id, request.RegionIds);
|
||||||
|
await repo.ReplaceRouteScopesAsync(id, request.RouteIds);
|
||||||
|
await repo.ReplaceStoreScopesAsync(id, request.StoreIds);
|
||||||
|
await uow.CommitAsync(ct);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeactivateAsync(Guid id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var user = await repo.GetByIdAsync(id);
|
||||||
|
if (user is null || !user.IsActive) return false;
|
||||||
|
await uow.BeginAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
repo.Remove(user);
|
||||||
|
await uow.CommitAsync(ct);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user