Files
pas/Api/Controllers/UserController.cs

483 lines
17 KiB
C#

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<UserController> _logger;
public UserController(AppDbContext context, ILogger<UserController> logger)
{
_context = context;
_logger = logger;
}
/// <summary>
/// Get all users with pagination support
/// </summary>
/// <param name="page">Page number (default: 1)</param>
/// <param name="pageSize">Page size (default: 10, max: 100)</param>
/// <param name="search">Search term for email, first name, or last name</param>
/// <param name="isActive">Filter by active status</param>
/// <returns>Paginated list of users</returns>
[HttpGet]
public async Task<ActionResult<object>> 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");
}
}
/// <summary>
/// Get a specific user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>User details</returns>
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> 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");
}
}
/// <summary>
/// Get the current user's profile
/// </summary>
/// <returns>Current user's profile</returns>
[HttpGet("me")]
public async Task<ActionResult<UserDto>> 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");
}
}
/// <summary>
/// Create a new user
/// </summary>
/// <param name="request">User creation request</param>
/// <returns>Created user</returns>
[HttpPost]
public async Task<ActionResult<UserDto>> 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<UserOAuthProviderDto>()
};
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");
}
}
/// <summary>
/// Update a user
/// </summary>
/// <param name="id">User ID</param>
/// <param name="request">User update request</param>
/// <returns>No content on success</returns>
[HttpPut("{id}")]
public async Task<IActionResult> 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");
}
}
/// <summary>
/// Update the current user's profile
/// </summary>
/// <param name="request">User update request</param>
/// <returns>No content on success</returns>
[HttpPut("me")]
public async Task<IActionResult> 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");
}
}
/// <summary>
/// Delete a user (soft delete by setting IsActive to false)
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> 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");
}
}
/// <summary>
/// Permanently delete a user and all associated OAuth providers
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}/permanent")]
public async Task<IActionResult> 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");
}
}
/// <summary>
/// Reactivate a soft-deleted user
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpPost("{id}/reactivate")]
public async Task<IActionResult> 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");
}
}
/// <summary>
/// Get user statistics
/// </summary>
/// <returns>User statistics</returns>
[HttpGet("statistics")]
public async Task<ActionResult<object>> 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");
}
}
}
}