using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nuuru.Server.Auth; using Nuuru.Server.DTOs.Admin; using Nuuru.Server.Extensions; using Nuuru.Server.Models; using Nuuru.Server.Services; namespace Nuuru.Server.Controllers { [ApiController] [Route("api/[controller]")] [Authorize] public class AdminController : ControllerBase { private readonly IAdminService _adminService; private readonly ILogger _logger; public AdminController( IAdminService adminService, ILogger logger) { _adminService = adminService; _logger = logger; } [Authorize(Policy = Permissions.Admin.SystemSettings)] [HttpGet("stats")] public async Task GetActivityStats([FromQuery] ActivityStatsQueryDto request) { try { if (request.Days is < 1 or > 366) return BadRequest(new { error = "Days must be between 1 and 366." }); if (request.DateFrom.HasValue && request.DateTo.HasValue) { if (request.DateFrom.Value > request.DateTo.Value) return BadRequest(new { error = "dateFrom must be on or before dateTo." }); if (request.DateTo.Value.DayNumber - request.DateFrom.Value.DayNumber >= 366) return BadRequest(new { error = "Date range must not exceed 366 days." }); } var stats = await _adminService.GetActivityStatsAsync(request); return Ok(stats); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving activity stats"); return StatusCode(500, new { error = "Failed to retrieve activity stats" }); } } /// /// Search and list users with pagination /// [Authorize(Policy = Permissions.Admin.ManageUsers)] [HttpGet("users")] public async Task SearchUsers([FromQuery] UserSearchRequest request) { try { var result = await _adminService.SearchUsersAsync( request.Search, request.Role, request.Page, request.PageSize); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error searching users"); return StatusCode(500, new { error = "Failed to search users" }); } } /// /// Get detailed information about a specific user /// [Authorize(Policy = Permissions.Admin.ManageUsers)] [HttpGet("users/{userId:guid}")] public async Task GetUser(Guid userId) { try { var user = await _adminService.GetUserByIdAsync(userId); if (user == null) { return NotFound(new { error = "User not found" }); } return Ok(user); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving user {UserId}", userId); return StatusCode(500, new { error = "Failed to retrieve user" }); } } /// /// Create a new user account (admin) /// [Authorize(Policy = Permissions.Admin.ManageUsers)] [HttpPost("users")] public async Task CreateUser([FromBody] AdminCreateUserRequest request) { var adminId = User.GetUserId(); if (adminId == null) return Unauthorized(); var (success, user, error) = await _adminService.CreateUserAsync(request.UserName, request.Password, adminId.Value); if (!success || user == null) return BadRequest(new { error }); // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = user.UserName; HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return StatusCode(201, user); } /// /// Change a user's password (admin override) /// [Authorize(Policy = Permissions.Admin.ManageUsers)] [HttpPost("users/{userId:guid}/change-password")] public async Task ChangeUserPassword(Guid userId, [FromBody] AdminChangePasswordRequest request) { var adminId = User.GetUserId(); if (adminId == null) return Unauthorized(); var targetUser = await _adminService.GetUserByIdAsync(userId); var (success, error) = await _adminService.ChangeUserPasswordAsync(userId, request.NewPassword, adminId.Value); if (!success) { if (error == "User not found") return NotFound(new { error }); return BadRequest(new { error }); } // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = targetUser?.UserName ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Password changed successfully" }); } /// /// Update a user's profile (status, biography, and admin-only flags) /// [Authorize(Policy = Permissions.Admin.ManageUsers)] [HttpPut("users/{userId:guid}/profile")] public async Task UpdateUserProfile(Guid userId, [FromBody] AdminUpdateProfileRequest request) { var adminId = User.GetUserId(); if (adminId == null) return Unauthorized(); var targetUser = await _adminService.GetUserByIdAsync(userId); var (success, error) = await _adminService.UpdateUserProfileAsync(userId, request.Status, request.Biography, request.IsBabyMode, adminId.Value); if (!success) { if (error == "User not found") return NotFound(new { error }); return BadRequest(new { error }); } // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = targetUser?.UserName ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Profile updated successfully" }); } /// /// Get a preview of what will be deleted when nuking a user, and a token to authorize the nuke /// [Authorize(Policy = Permissions.Admin.NukeUser)] [HttpPost("users/{userId:guid}/nuke/preview")] public async Task NukeUserPreview(Guid userId) { var adminId = User.GetUserId(); if (adminId == null) return Unauthorized(); var (success, preview, error) = await _adminService.GetNukePreviewAsync(userId, adminId.Value); if (!success) { if (error == "User not found") return NotFound(new { error }); return BadRequest(new { error }); } return Ok(preview); } /// /// Permanently delete a user and all their content (posts, comments, etc.) /// [Authorize(Policy = Permissions.Admin.NukeUser)] [HttpDelete("users/{userId:guid}/nuke")] public async Task NukeUser(Guid userId, [FromBody] NukeUserRequest request) { var adminId = User.GetUserId(); if (adminId == null) return Unauthorized(); var targetUser = await _adminService.GetUserByIdAsync(userId); var (success, error) = await _adminService.NukeUserAsync(userId, adminId.Value, request.Token); if (!success) { if (error == "User not found") return NotFound(new { error }); return BadRequest(new { error }); } // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = targetUser?.UserName ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "User and all associated content have been permanently deleted" }); } } }