feat(backend): Implement Day 6 Role Management API
Add complete role management functionality for tenant administrators to manage user roles within their tenants.
Changes:
- Extended IUserTenantRoleRepository with pagination, role counting, and last owner check methods
- Extended IUserRepository with GetByIdAsync(Guid) and GetByIdsAsync for flexible user retrieval
- Extended IRefreshTokenRepository with GetByUserAndTenantAsync and UpdateRangeAsync
- Implemented repository methods in Infrastructure layer
- Created DTOs: UserWithRoleDto and PagedResultDto<T>
- Implemented ListTenantUsersQuery with pagination support
- Implemented AssignUserRoleCommand to assign/update user roles
- Implemented RemoveUserFromTenantCommand with token revocation
- Created TenantUsersController with 4 endpoints (list, assign, remove, get-roles)
- Added comprehensive PowerShell test script
Security Features:
- Only TenantOwner can assign/update/remove roles
- Prevents removal of last TenantOwner (lockout protection)
- Prevents manual assignment of AIAgent role (reserved for MCP)
- Cross-tenant access protection
- Automatic refresh token revocation when user removed
API Endpoints:
- GET /api/tenants/{id}/users - List users with roles (paginated)
- POST /api/tenants/{id}/users/{userId}/role - Assign/update role
- DELETE /api/tenants/{id}/users/{userId} - Remove user from tenant
- GET /api/tenants/roles - Get available roles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using ColaFlow.Modules.Identity.Application.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace ColaFlow.Modules.Identity.Application.Queries.ListTenantUsers;
|
||||
|
||||
public record ListTenantUsersQuery(
|
||||
Guid TenantId,
|
||||
int PageNumber = 1,
|
||||
int PageSize = 20,
|
||||
string? SearchTerm = null) : IRequest<PagedResultDto<UserWithRoleDto>>;
|
||||
@@ -0,0 +1,58 @@
|
||||
using ColaFlow.Modules.Identity.Application.Dtos;
|
||||
using ColaFlow.Modules.Identity.Domain.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace ColaFlow.Modules.Identity.Application.Queries.ListTenantUsers;
|
||||
|
||||
public class ListTenantUsersQueryHandler : IRequestHandler<ListTenantUsersQuery, PagedResultDto<UserWithRoleDto>>
|
||||
{
|
||||
private readonly IUserTenantRoleRepository _userTenantRoleRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public ListTenantUsersQueryHandler(
|
||||
IUserTenantRoleRepository userTenantRoleRepository,
|
||||
IUserRepository userRepository)
|
||||
{
|
||||
_userTenantRoleRepository = userTenantRoleRepository;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task<PagedResultDto<UserWithRoleDto>> Handle(
|
||||
ListTenantUsersQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var (roles, totalCount) = await _userTenantRoleRepository.GetTenantUsersWithRolesAsync(
|
||||
request.TenantId,
|
||||
request.PageNumber,
|
||||
request.PageSize,
|
||||
request.SearchTerm,
|
||||
cancellationToken);
|
||||
|
||||
var userDtos = new List<UserWithRoleDto>();
|
||||
|
||||
foreach (var role in roles)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(role.UserId, cancellationToken);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
userDtos.Add(new UserWithRoleDto(
|
||||
user.Id,
|
||||
user.Email.Value,
|
||||
user.FullName.Value,
|
||||
role.Role.ToString(),
|
||||
role.AssignedAt,
|
||||
user.EmailVerifiedAt.HasValue));
|
||||
}
|
||||
}
|
||||
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize);
|
||||
|
||||
return new PagedResultDto<UserWithRoleDto>(
|
||||
userDtos,
|
||||
totalCount,
|
||||
request.PageNumber,
|
||||
request.PageSize,
|
||||
totalPages);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user