using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; /// /// User directory, management, and password operations. /// [ApiController] [Route("api/v1/users")] [Produces("application/json")] [Authorize] public class UsersController : ControllerBase { private readonly IUserDirectoryService _users; public UsersController(IUserDirectoryService users) => _users = users; /// /// Lists active users, optionally filtered by role. /// [HttpGet] [Authorize(Roles = "INTAKE_CLERK,ADMINISTRATOR")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task List([FromQuery] string? role) { UserRole? parsedRole = null; if (!string.IsNullOrWhiteSpace(role)) parsedRole = UserRoleExtensions.FromDbString(role); var results = await _users.ListByRoleAsync(parsedRole); return Ok(ApiResponse>.Ok(results)); } /// /// Creates a new user. Admin only. /// [HttpPost] [Authorize(Roles = "ADMINISTRATOR")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task Create([FromBody] CreateUserRequest request) { var result = await _users.CreateAsync(request); return StatusCode(201, ApiResponse.Created(result)); } /// /// Updates user details (fullName, role, isActive). Admin only. /// [HttpPatch("{id:guid}")] [Authorize(Roles = "ADMINISTRATOR")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task Update(Guid id, [FromBody] UpdateUserRequest request) { var result = await _users.UpdateAsync(id, request); return Ok(ApiResponse.Ok(result)); } /// /// Admin password reset for any user. /// [HttpPost("{id:guid}/reset-password")] [Authorize(Roles = "ADMINISTRATOR")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task ResetPassword(Guid id, [FromBody] ResetPasswordRequest request) { await _users.ResetPasswordAsync(id, request.NewPassword); return NoContent(); } /// /// Self-service password change. Requires current password. /// [HttpPost("me/change-password")] [Authorize] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task ChangePassword([FromBody] ChangePasswordRequest request) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); await _users.ChangePasswordAsync(userId, request.CurrentPassword, request.NewPassword); return NoContent(); } }