162 lines
6.0 KiB
C#
162 lines
6.0 KiB
C#
using AutoMapper;
|
|||
|
|
using MediatR;
|
||
|
|
using Microsoft.AspNetCore.Authorization;
|
||
|
|
using Microsoft.AspNetCore.Mvc;
|
||
|
|
using Microsoft.EntityFrameworkCore;
|
||
|
|
using mws.backend.dotnet.application.Common;
|
||
|
|
using mws.backend.dotnet.application.Permissions;
|
||
|
|
using mws.backend.dotnet.application.Users;
|
||
|
|
using mws.backend.dotnet.domain.Users;
|
||
|
|
using mws.backend.dotnet.infrastructure.Persistence;
|
||
|
|
|
||
|
|
namespace mws.backend.dotnet.api.Controllers;
|
||
|
|
|
||
|
|
[ApiController]
|
||
|
|
[Route("api/users")]
|
||
|
|
[Authorize]
|
||
|
|
public class UsersController(ISender sender, AppDbContext db, IPermissionService permissions, IMapper mapper) : ControllerBase
|
||
|
|
{
|
||
|
|
[HttpGet]
|
||
|
|
public async Task<ActionResult<PagedResult<UserDto>>> GetAll([FromQuery] string? q, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken ct = default)
|
||
|
|
{
|
||
|
|
return Ok(await sender.Send(new GetUsersQuery(User.GetUserId(), q, page, pageSize), ct));
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost]
|
||
|
|
public async Task<ActionResult<UserDto>> Create([FromBody] CreateUserRequest request, CancellationToken ct)
|
||
|
|
{
|
||
|
|
return Ok(await sender.Send(new CreateUserCommand(User.GetUserId(), request), ct));
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPut("{id:guid}")]
|
||
|
|
public async Task<ActionResult<UserDto>> Update(Guid id, [FromBody] UpdateUserRequest request, CancellationToken ct)
|
||
|
|
{
|
||
|
|
return Ok(await sender.Send(new UpdateUserCommand(User.GetUserId(), id, request), ct));
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpDelete("{id:guid}")]
|
||
|
|
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await sender.Send(new DeleteUserCommand(User.GetUserId(), id), ct);
|
||
|
|
return NoContent();
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("{id:guid}/reset-password")]
|
||
|
|
public async Task<IActionResult> ResetPassword(Guid id, [FromBody] ResetPasswordRequest request, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await sender.Send(new ResetPasswordCommand(User.GetUserId(), id, request), ct);
|
||
|
|
return NoContent();
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpGet("list")]
|
||
|
|
public async Task<ActionResult<List<UserDto>>> GetList([FromQuery] string? q, CancellationToken ct)
|
||
|
|
{
|
||
|
|
var query = db.Users.AsQueryable();
|
||
|
|
if (!string.IsNullOrWhiteSpace(q))
|
||
|
|
{
|
||
|
|
var lower = q.Trim().ToLower();
|
||
|
|
query = query.Where(u => u.Username.ToLower().Contains(lower) || u.DisplayName.ToLower().Contains(lower));
|
||
|
|
}
|
||
|
|
|
||
|
|
var users = await query.OrderBy(u => u.DisplayName).ToListAsync(ct);
|
||
|
|
return Ok(mapper.Map<List<UserDto>>(users));
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpGet("{id:guid}/roles")]
|
||
|
|
public async Task<ActionResult<UserRoleDetailDto>> GetUserRoles(Guid id, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await permissions.EnsureAsync(User.GetUserId(), "permissions", PermissionAction.View, ct);
|
||
|
|
|
||
|
|
var user = await db.Users
|
||
|
|
.Include(u => u.UserRoles)
|
||
|
|
.ThenInclude(ur => ur.Role)
|
||
|
|
.FirstOrDefaultAsync(u => u.Id == id, ct);
|
||
|
|
|
||
|
|
if (user is null) return NotFound("User not found");
|
||
|
|
|
||
|
|
var allRoles = await db.Roles.Include(r => r.Permissions).OrderBy(r => r.Name).ToListAsync(ct);
|
||
|
|
var assignedRoleIds = user.UserRoles.Select(ur => ur.RoleId).ToHashSet();
|
||
|
|
|
||
|
|
var assigned = allRoles.Where(r => assignedRoleIds.Contains(r.Id)).ToList();
|
||
|
|
var unassigned = allRoles.Where(r => !assignedRoleIds.Contains(r.Id)).ToList();
|
||
|
|
|
||
|
|
return Ok(new UserRoleDetailDto
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
Username = user.Username,
|
||
|
|
DisplayName = user.DisplayName,
|
||
|
|
AssignedRoles = mapper.Map<List<RoleDto>>(assigned),
|
||
|
|
UnassignedRoles = mapper.Map<List<RoleDto>>(unassigned)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpPost("{id:guid}/roles")]
|
||
|
|
public async Task<IActionResult> AssignRole(Guid id, [FromBody] AssignRoleRequest req, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await permissions.EnsureAsync(User.GetUserId(), "permissions", PermissionAction.Edit, ct);
|
||
|
|
|
||
|
|
var user = await db.Users.Include(u => u.UserRoles).FirstOrDefaultAsync(u => u.Id == id, ct);
|
||
|
|
if (user is null) return NotFound("User not found");
|
||
|
|
|
||
|
|
if (!user.UserRoles.Any(ur => ur.RoleId == req.RoleId))
|
||
|
|
{
|
||
|
|
user.UserRoles.Add(new UserRole { UserId = id, RoleId = req.RoleId });
|
||
|
|
await db.SaveChangesAsync(ct);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NoContent();
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpDelete("{id:guid}/roles/{roleId:guid}")]
|
||
|
|
public async Task<IActionResult> UnassignRole(Guid id, Guid roleId, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await permissions.EnsureAsync(User.GetUserId(), "permissions", PermissionAction.Edit, ct);
|
||
|
|
|
||
|
|
var ur = await db.UserRoles.FirstOrDefaultAsync(x => x.UserId == id && x.RoleId == roleId, ct);
|
||
|
|
if (ur != null)
|
||
|
|
{
|
||
|
|
db.UserRoles.Remove(ur);
|
||
|
|
await db.SaveChangesAsync(ct);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NoContent();
|
||
|
|
}
|
||
|
|
|
||
|
|
[HttpGet("{id:guid}/permissions")]
|
||
|
|
public async Task<ActionResult<List<MenuItemDto>>> GetUserPermissions(Guid id, CancellationToken ct)
|
||
|
|
{
|
||
|
|
await permissions.EnsureAsync(User.GetUserId(), "permissions", PermissionAction.View, ct);
|
||
|
|
|
||
|
|
var roleIds = await db.UserRoles.Where(ur => ur.UserId == id).Select(ur => ur.RoleId).ToListAsync(ct);
|
||
|
|
var permissionsList = await db.RolePermissions.Where(p => roleIds.Contains(p.RoleId)).ToListAsync(ct);
|
||
|
|
|
||
|
|
var merged = permissionsList.GroupBy(p => p.Screen).ToDictionary(
|
||
|
|
g => g.Key,
|
||
|
|
g => new
|
||
|
|
{
|
||
|
|
CanView = g.Any(p => p.CanView),
|
||
|
|
CanCreate = g.Any(p => p.CanCreate),
|
||
|
|
CanEdit = g.Any(p => p.CanEdit),
|
||
|
|
CanDelete = g.Any(p => p.CanDelete),
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
var res = ScreenCatalog.Screens.Select(s =>
|
||
|
|
{
|
||
|
|
merged.TryGetValue(s.Key, out var p);
|
||
|
|
return new MenuItemDto
|
||
|
|
{
|
||
|
|
Key = s.Key,
|
||
|
|
Label = s.Label,
|
||
|
|
Path = s.Path,
|
||
|
|
CanView = p?.CanView ?? false,
|
||
|
|
CanCreate = p?.CanCreate ?? false,
|
||
|
|
CanEdit = p?.CanEdit ?? false,
|
||
|
|
CanDelete = p?.CanDelete ?? false,
|
||
|
|
};
|
||
|
|
}).ToList();
|
||
|
|
|
||
|
|
return Ok(res);
|
||
|
|
}
|
||
|
|
}
|