using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Api.Models; using Api.Models.DTOs; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; namespace Api.Controllers { [ApiController] [Authorize] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly AppDbContext _context; private readonly ILogger _logger; public UserController(AppDbContext context, ILogger logger) { _context = context; _logger = logger; } /// /// Get all users with pagination support /// /// Page number (default: 1) /// Page size (default: 10, max: 100) /// Search term for email, first name, or last name /// Filter by active status /// Paginated list of users [HttpGet] public async Task> GetUsers( [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? search = null, [FromQuery] bool? isActive = null) { try { // Validate pagination parameters page = Math.Max(1, page); pageSize = Math.Min(100, Math.Max(1, pageSize)); var query = _context.Users.Include(u => u.OAuthProviders).AsQueryable(); // Apply filters if (isActive.HasValue) { query = query.Where(u => u.IsActive == isActive.Value); } if (!string.IsNullOrEmpty(search)) { var searchTerm = search.ToLower(); query = query.Where(u => u.Email.ToLower().Contains(searchTerm) || (u.FirstName != null && u.FirstName.ToLower().Contains(searchTerm)) || (u.LastName != null && u.LastName.ToLower().Contains(searchTerm))); } var totalCount = await query.CountAsync(); var users = await query .OrderByDescending(u => u.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(u => new UserDto { Id = u.Id, Email = u.Email, FirstName = u.FirstName, LastName = u.LastName, ProfilePictureUrl = u.ProfilePictureUrl, CreatedAt = u.CreatedAt, LastLoginAt = u.LastLoginAt, IsActive = u.IsActive, OAuthProviders = u.OAuthProviders.Select(op => new UserOAuthProviderDto { Id = op.Id, Provider = op.Provider, ProviderId = op.ProviderId, ProviderEmail = op.ProviderEmail, ProviderName = op.ProviderName, CreatedAt = op.CreatedAt, LastUsedAt = op.LastUsedAt }).ToList() }) .ToListAsync(); return Ok(new { users, pagination = new { page, pageSize, totalCount, totalPages = (int)Math.Ceiling(totalCount / (double)pageSize), hasNext = page * pageSize < totalCount, hasPrevious = page > 1 } }); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving users"); return StatusCode(500, "Internal server error while retrieving users"); } } /// /// Get a specific user by ID /// /// User ID /// User details [HttpGet("{id}")] public async Task> GetUser(int id) { try { var user = await _context.Users .Include(u => u.OAuthProviders) .FirstOrDefaultAsync(u => u.Id == id); if (user == null) { return NotFound($"User with ID {id} not found"); } var userDto = new UserDto { Id = user.Id, Email = user.Email, FirstName = user.FirstName, LastName = user.LastName, ProfilePictureUrl = user.ProfilePictureUrl, CreatedAt = user.CreatedAt, LastLoginAt = user.LastLoginAt, IsActive = user.IsActive, OAuthProviders = user.OAuthProviders.Select(op => new UserOAuthProviderDto { Id = op.Id, Provider = op.Provider, ProviderId = op.ProviderId, ProviderEmail = op.ProviderEmail, ProviderName = op.ProviderName, CreatedAt = op.CreatedAt, LastUsedAt = op.LastUsedAt }).ToList() }; return Ok(userDto); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving user with ID {UserId}", id); return StatusCode(500, "Internal server error while retrieving user"); } } /// /// Get the current user's profile /// /// Current user's profile [HttpGet("me")] public async Task> GetCurrentUser() { try { var userIdClaim = User.FindFirst("user_id")?.Value; if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out var userId)) { return Unauthorized("Invalid user token"); } return await GetUser(userId); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving current user profile"); return StatusCode(500, "Internal server error while retrieving current user"); } } /// /// Create a new user /// /// User creation request /// Created user [HttpPost] public async Task> CreateUser([FromBody] CreateUserRequest request) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { // Check if user with this email already exists var existingUser = await _context.Users .FirstOrDefaultAsync(u => u.Email == request.Email); if (existingUser != null) { return Conflict($"User with email {request.Email} already exists"); } var user = new User { Email = request.Email, FirstName = request.FirstName, LastName = request.LastName, ProfilePictureUrl = request.ProfilePictureUrl, IsActive = request.IsActive, CreatedAt = DateTime.UtcNow }; _context.Users.Add(user); await _context.SaveChangesAsync(); _logger.LogInformation("Created new user with ID {UserId} and email {Email}", user.Id, user.Email); var userDto = new UserDto { Id = user.Id, Email = user.Email, FirstName = user.FirstName, LastName = user.LastName, ProfilePictureUrl = user.ProfilePictureUrl, CreatedAt = user.CreatedAt, LastLoginAt = user.LastLoginAt, IsActive = user.IsActive, OAuthProviders = new List() }; return CreatedAtAction(nameof(GetUser), new { id = user.Id }, userDto); } catch (Exception ex) { _logger.LogError(ex, "Error creating user with email {Email}", request.Email); return StatusCode(500, "Internal server error while creating user"); } } /// /// Update a user /// /// User ID /// User update request /// No content on success [HttpPut("{id}")] public async Task UpdateUser(int id, [FromBody] UpdateUserRequest request) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { var user = await _context.Users.FindAsync(id); if (user == null) { return NotFound($"User with ID {id} not found"); } // Update only provided fields if (request.FirstName != null) { user.FirstName = request.FirstName; } if (request.LastName != null) { user.LastName = request.LastName; } if (request.ProfilePictureUrl != null) { user.ProfilePictureUrl = request.ProfilePictureUrl; } if (request.IsActive.HasValue) { user.IsActive = request.IsActive.Value; } _context.Entry(user).State = EntityState.Modified; await _context.SaveChangesAsync(); _logger.LogInformation("Updated user with ID {UserId}", id); return NoContent(); } catch (DbUpdateConcurrencyException) { if (!await _context.Users.AnyAsync(e => e.Id == id)) { return NotFound($"User with ID {id} not found"); } else { throw; } } catch (Exception ex) { _logger.LogError(ex, "Error updating user with ID {UserId}", id); return StatusCode(500, "Internal server error while updating user"); } } /// /// Update the current user's profile /// /// User update request /// No content on success [HttpPut("me")] public async Task UpdateCurrentUser([FromBody] UpdateUserRequest request) { try { var userIdClaim = User.FindFirst("user_id")?.Value; if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out var userId)) { return Unauthorized("Invalid user token"); } return await UpdateUser(userId, request); } catch (Exception ex) { _logger.LogError(ex, "Error updating current user profile"); return StatusCode(500, "Internal server error while updating current user"); } } /// /// Delete a user (soft delete by setting IsActive to false) /// /// User ID /// No content on success [HttpDelete("{id}")] public async Task DeleteUser(int id) { try { var user = await _context.Users.FindAsync(id); if (user == null) { return NotFound($"User with ID {id} not found"); } // Soft delete by setting IsActive to false user.IsActive = false; _context.Entry(user).State = EntityState.Modified; await _context.SaveChangesAsync(); _logger.LogInformation("Soft deleted user with ID {UserId}", id); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error deleting user with ID {UserId}", id); return StatusCode(500, "Internal server error while deleting user"); } } /// /// Permanently delete a user and all associated OAuth providers /// /// User ID /// No content on success [HttpDelete("{id}/permanent")] public async Task PermanentlyDeleteUser(int id) { try { var user = await _context.Users .Include(u => u.OAuthProviders) .FirstOrDefaultAsync(u => u.Id == id); if (user == null) { return NotFound($"User with ID {id} not found"); } // Remove all OAuth providers first due to foreign key constraints _context.UserOAuthProviders.RemoveRange(user.OAuthProviders); // Remove the user _context.Users.Remove(user); await _context.SaveChangesAsync(); _logger.LogInformation("Permanently deleted user with ID {UserId} and {ProviderCount} OAuth providers", id, user.OAuthProviders.Count); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error permanently deleting user with ID {UserId}", id); return StatusCode(500, "Internal server error while permanently deleting user"); } } /// /// Reactivate a soft-deleted user /// /// User ID /// No content on success [HttpPost("{id}/reactivate")] public async Task ReactivateUser(int id) { try { var user = await _context.Users.FindAsync(id); if (user == null) { return NotFound($"User with ID {id} not found"); } user.IsActive = true; _context.Entry(user).State = EntityState.Modified; await _context.SaveChangesAsync(); _logger.LogInformation("Reactivated user with ID {UserId}", id); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Error reactivating user with ID {UserId}", id); return StatusCode(500, "Internal server error while reactivating user"); } } /// /// Get user statistics /// /// User statistics [HttpGet("statistics")] public async Task> GetUserStatistics() { try { var totalUsers = await _context.Users.CountAsync(); var activeUsers = await _context.Users.CountAsync(u => u.IsActive); var inactiveUsers = totalUsers - activeUsers; var usersWithLogin = await _context.Users.CountAsync(u => u.LastLoginAt != null); var recentUsers = await _context.Users.CountAsync(u => u.CreatedAt >= DateTime.UtcNow.AddDays(-30)); var providerStats = await _context.UserOAuthProviders .GroupBy(op => op.Provider) .Select(g => new { Provider = g.Key.ToString(), Count = g.Count(), UniqueUsers = g.Select(op => op.UserId).Distinct().Count() }) .ToListAsync(); return Ok(new { totalUsers, activeUsers, inactiveUsers, usersWithLogin, recentUsers, providerStatistics = providerStats }); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving user statistics"); return StatusCode(500, "Internal server error while retrieving statistics"); } } } }