using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Models; using LittleShop.Services; using System.Security.Cryptography; using System.Text; namespace LittleShop.Controllers; [ApiController] [Route("api/[controller]")] [Authorize(Policy = "AdminOnly")] public class UsersController : ControllerBase { private readonly LittleShopContext _context; private readonly ILogger _logger; public UsersController(LittleShopContext context, ILogger logger) { _context = context; _logger = logger; } [HttpGet] public async Task>> GetUsers() { try { var users = await _context.Users .Where(u => u.IsActive) .Select(u => new UserDto { Id = u.Id, Username = u.Username, Email = u.Email, Role = u.Role, CreatedAt = u.CreatedAt, IsActive = u.IsActive }) .ToListAsync(); return Ok(users); } catch (Exception ex) { _logger.LogError(ex, "Error fetching users"); return StatusCode(500, new { message = "Error fetching users", error = ex.Message }); } } [HttpGet("{id}")] public async Task> GetUser(Guid id) { try { var user = await _context.Users.FindAsync(id); if (user == null || !user.IsActive) { return NotFound(); } return Ok(new UserDto { Id = user.Id, Username = user.Username, Email = user.Email, Role = user.Role, CreatedAt = user.CreatedAt, IsActive = user.IsActive }); } catch (Exception ex) { _logger.LogError(ex, "Error fetching user {UserId}", id); return StatusCode(500, new { message = "Error fetching user", error = ex.Message }); } } [HttpPost] public async Task> CreateUser([FromBody] CreateUserDto createUserDto) { try { // Check if username already exists if (await _context.Users.AnyAsync(u => u.Username == createUserDto.Username)) { return BadRequest(new { message = "Username already exists" }); } // Check if email already exists if (!string.IsNullOrEmpty(createUserDto.Email) && await _context.Users.AnyAsync(u => u.Email == createUserDto.Email)) { return BadRequest(new { message = "Email already exists" }); } var user = new User { Id = Guid.NewGuid(), Username = createUserDto.Username, Email = createUserDto.Email, Role = createUserDto.Role ?? "Staff", PasswordHash = HashPassword(createUserDto.Password), CreatedAt = DateTime.UtcNow, IsActive = true }; _context.Users.Add(user); await _context.SaveChangesAsync(); _logger.LogInformation("User created: {Username}", user.Username); return CreatedAtAction(nameof(GetUser), new { id = user.Id }, new UserDto { Id = user.Id, Username = user.Username, Email = user.Email, Role = user.Role, CreatedAt = user.CreatedAt, IsActive = user.IsActive }); } catch (Exception ex) { _logger.LogError(ex, "Error creating user"); return StatusCode(500, new { message = "Error creating user", error = ex.Message }); } } [HttpPut("{id}")] public async Task UpdateUser(Guid id, [FromBody] UpdateUserDto updateUserDto) { try { var user = await _context.Users.FindAsync(id); if (user == null) { return NotFound(); } // Check if username is being changed to an existing one if (!string.IsNullOrEmpty(updateUserDto.Username) && updateUserDto.Username != user.Username && await _context.Users.AnyAsync(u => u.Username == updateUserDto.Username)) { return BadRequest(new { message = "Username already exists" }); } // Check if email is being changed to an existing one if (!string.IsNullOrEmpty(updateUserDto.Email) && updateUserDto.Email != user.Email && await _context.Users.AnyAsync(u => u.Email == updateUserDto.Email)) { return BadRequest(new { message = "Email already exists" }); } // Update fields if (!string.IsNullOrEmpty(updateUserDto.Username)) user.Username = updateUserDto.Username; if (updateUserDto.Email != null) user.Email = updateUserDto.Email; if (!string.IsNullOrEmpty(updateUserDto.Role)) user.Role = updateUserDto.Role; if (!string.IsNullOrEmpty(updateUserDto.Password)) user.PasswordHash = HashPassword(updateUserDto.Password); await _context.SaveChangesAsync(); _logger.LogInformation("User updated: {UserId}", id); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error updating user {UserId}", id); return StatusCode(500, new { message = "Error updating user", error = ex.Message }); } } [HttpDelete("{id}")] public async Task DeleteUser(Guid id) { try { var user = await _context.Users.FindAsync(id); if (user == null) { return NotFound(); } // Don't delete the last admin user if (user.Role == "Admin") { var adminCount = await _context.Users.CountAsync(u => u.Role == "Admin" && u.IsActive); if (adminCount <= 1) { return BadRequest(new { message = "Cannot delete the last admin user" }); } } // Soft delete user.IsActive = false; await _context.SaveChangesAsync(); _logger.LogInformation("User deleted: {UserId}", id); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error deleting user {UserId}", id); return StatusCode(500, new { message = "Error deleting user", error = ex.Message }); } } private static string HashPassword(string password) { using var pbkdf2 = new Rfc2898DeriveBytes( password, salt: Encoding.UTF8.GetBytes("LittleShopSalt2024!"), iterations: 100000, HashAlgorithmName.SHA256); return Convert.ToBase64String(pbkdf2.GetBytes(32)); } }