using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Nuuru.Server.Auth; using Nuuru.Server.DTOs; using Nuuru.Server.DTOs.Booru; using Nuuru.Server.DTOs.User; using Nuuru.Server.Extensions; using Nuuru.Server.Models; using Nuuru.Server.Services; namespace Nuuru.Server.Controllers { [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly IUserService _userService; private readonly IFavoriteService _favoriteService; private readonly UserManager _userManager; public UserController( IUserService userService, IFavoriteService favoriteService, UserManager userManager) { _userService = userService; _favoriteService = favoriteService; _userManager = userManager; } [HttpGet("{username}")] [AllowAnonymous] public async Task GetUserProfile(string username) { var user = await _userManager.FindByNameAsync(username); if (user == null || user.IsSystemAccount) return NotFound(new { error = "User not found" }); var profile = await _userService.GetUserProfileAsync(user.Id); return Ok(profile); } /// /// Get basic user info by ID (for messaging recipient lookup) /// [HttpGet("id/{userId:guid}")] [AllowAnonymous] public async Task GetUserById(Guid userId) { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) return NotFound(new { error = "User not found" }); var avatarUrl = user.AvatarStorageIdentifier != null ? $"/api/User/{user.UserName}/avatar" : null; return Ok(new { id = user.Id.ToString(), userName = user.UserName, avatarUrl }); } [HttpGet("{username}/stats")] [AllowAnonymous] public async Task GetUserStats(string username) { var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var stats = await _userService.GetUserStatsAsync(user.Id); return Ok(stats); } [HttpGet("{username}/posts")] [AllowAnonymous] public async Task GetUserPosts( string username, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { if (page < 1) return BadRequest(new { error = "Page must be greater than 0" }); if (pageSize < 1 || pageSize > 100) return BadRequest(new { error = "Page size must be between 1 and 100" }); var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var posts = await _userService.GetUserPostsAsync(user.Id, page, pageSize); return Ok(posts); } [HttpGet("{username}/threads")] [AllowAnonymous] public async Task GetUserThreads( string username, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { if (page < 1) return BadRequest(new { error = "Page must be greater than 0" }); if (pageSize < 1 || pageSize > 100) return BadRequest(new { error = "Page size must be between 1 and 100" }); var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var threads = await _userService.GetUserThreadsAsync(user.Id, page, pageSize); return Ok(threads); } [HttpPut("profile")] [Authorize] public async Task UpdateProfile([FromBody] UpdateProfileDto updateDto) { var userId = User.GetUserId(); if (userId == null) return Unauthorized(new { error = "User ID not found in token" }); if ((updateDto.Biography != null || updateDto.Status != null) && !User.HasPermission(Permissions.User.EditBio)) return Forbid(); var result = await _userService.UpdateProfileAsync(userId.Value, updateDto); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = User.GetUserName() ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Profile updated successfully" }); } [HttpGet("{username}/avatar")] [AllowAnonymous] public async Task GetAvatar(string username) { var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var file = await _userService.GetAvatarAsync(user.Id); if (file == null) return NotFound(new { error = "Avatar not found" }); Response.SetFileCacheHeaders(); return File(file.Stream, file.Metadata.ContentType); } [HttpPost("avatar")] [Authorize] public async Task UploadAvatar(IFormFile file, [FromForm] int? cropX, [FromForm] int? cropY, [FromForm] int? cropSize) { if (file == null || file.Length == 0) return BadRequest(new { error = "No file provided" }); var userId = User.GetUserId(); if (userId == null) return Unauthorized(new { error = "User ID not found in token" }); CropRect? crop = null; if (cropX.HasValue && cropY.HasValue && cropSize.HasValue) crop = new CropRect(cropX.Value, cropY.Value, cropSize.Value); using var stream = file.OpenReadStream(); var result = await _userService.UploadAvatarAsync(userId.Value, stream, file.FileName, file.Length, crop); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = User.GetUserName() ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Avatar uploaded successfully", avatarUrl = result.AvatarUrl }); } [HttpDelete("avatar")] [Authorize] public async Task DeleteAvatar() { var userId = User.GetUserId(); if (userId == null) return Unauthorized(new { error = "User ID not found in token" }); var result = await _userService.DeleteAvatarAsync(userId.Value); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); // Set target for audit log HttpContext.Items[AuditLog.TargetIdKey] = User.GetUserName() ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Avatar deleted successfully" }); } [HttpGet("{username}/background")] [AllowAnonymous] public async Task GetBackground(string username) { var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var file = await _userService.GetBackgroundImageAsync(user.Id); if (file == null) return NotFound(new { error = "Background image not found" }); Response.SetFileCacheHeaders(); return File(file.Stream, file.Metadata.ContentType); } [HttpPost("background")] [Authorize] public async Task UploadBackground(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(new { error = "No file provided" }); var userId = User.GetUserId(); if (userId == null) return Unauthorized(new { error = "User ID not found in token" }); using var stream = file.OpenReadStream(); var result = await _userService.UploadBackgroundImageAsync(userId.Value, stream, file.FileName, file.Length); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); HttpContext.Items[AuditLog.TargetIdKey] = User.GetUserName() ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Background image uploaded successfully", backgroundImageUrl = result.BackgroundImageUrl }); } [HttpDelete("background")] [Authorize] public async Task DeleteBackground() { var userId = User.GetUserId(); if (userId == null) return Unauthorized(new { error = "User ID not found in token" }); var result = await _userService.DeleteBackgroundImageAsync(userId.Value); if (!result.Success) return BadRequest(new { error = result.ErrorMessage }); HttpContext.Items[AuditLog.TargetIdKey] = User.GetUserName() ?? userId.ToString(); HttpContext.Items[AuditLog.TargetTypeKey] = "User"; return Ok(new { message = "Background image deleted successfully" }); } [HttpGet("{username}/favorites")] [AllowAnonymous] public async Task GetUserFavorites( string username, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { if (page < 1) return BadRequest(new { error = "Page must be greater than 0" }); if (pageSize < 1 || pageSize > 100) return BadRequest(new { error = "Page size must be between 1 and 100" }); var user = await _userManager.FindByNameAsync(username); if (user == null) return NotFound(new { error = "User not found" }); var favorites = await _favoriteService.GetUserFavoritesAsync(user.Id, page, pageSize); var totalCount = await _favoriteService.GetUserFavoriteCountAsync(user.Id); return Ok(new PagedResult { Items = favorites.Select(p => p.ToDto()).ToList(), Page = page, PageSize = pageSize, TotalCount = totalCount }); } [HttpGet("search/mention")] [Authorize] public async Task SearchUsersForMention( [FromQuery] string query, [FromQuery] int limit = 10) { if (string.IsNullOrWhiteSpace(query)) return BadRequest(new { error = "Query is required" }); var users = await _userService.SearchUsersForMentionAsync(query, limit); return Ok(users); } } }