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.Forum; using Nuuru.Server.Services; namespace Nuuru.Server.Controllers { [ApiController] [Route("api/forum/categories")] public class ForumCategoryController : ControllerBase { private readonly IForumCategoryService _categoryService; private readonly IUserBadgeService _userBadgeService; private readonly ILogger _logger; public ForumCategoryController( IForumCategoryService categoryService, IUserBadgeService userBadgeService, ILogger logger) { _categoryService = categoryService; _userBadgeService = userBadgeService; _logger = logger; } /// /// Get all forum categories with stats /// [HttpGet] [AllowAnonymous] public async Task GetCategories() { try { var userId = User.GetUserId(); var categories = await _categoryService.GetVisibleCategoriesAsync(userId); var categoryDtos = new List(); var latestThreads = new List(); var categoryStats = new List<(int threadCount, int postCount)>(); foreach (var category in categories) { var stats = await _categoryService.GetCategoryStatsAsync(category.Id); var latestThread = await _categoryService.GetLatestThreadAsync(category.Id); categoryStats.Add(stats); if (latestThread != null) latestThreads.Add(latestThread); } // Batch-fetch display info for all latest thread authors and last post authors var authorIds = latestThreads .Where(t => t.Author != null) .Select(t => t.Author!.Id) .Concat(latestThreads.Where(t => t.LastPost?.Author != null).Select(t => t.LastPost!.Author!.Id)) .Distinct() .ToList(); var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync(authorIds); for (int i = 0; i < categories.Count(); i++) { var category = categories.ElementAt(i); var (threadCount, postCount) = categoryStats[i]; var latestThread = latestThreads.FirstOrDefault(t => t.CategoryId == category.Id); categoryDtos.Add(category.ToDto(threadCount, postCount, latestThread, displayInfoMap)); } return Ok(categoryDtos); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving forum categories"); return StatusCode(500, new { error = "Failed to retrieve categories" }); } } /// /// Get a single category by slug /// [HttpGet("{slug}")] [AllowAnonymous] public async Task GetCategory(string slug) { try { ForumCategory? category; if (slug.Equals("clan", StringComparison.OrdinalIgnoreCase)) { var userId = User.GetUserId(); if (!userId.HasValue) return StatusCode(403, new { error = "Log in to access your clan forum." }); category = await _categoryService.GetClanCategoryForUserAsync(userId.Value); if (category == null) return NotFound(new { error = "Your clan does not have a forum." }); } else { category = await _categoryService.GetCategoryBySlugAsync(slug); } if (category == null) { return NotFound(new { error = "Category not found" }); } var (threadCount, postCount) = await _categoryService.GetCategoryStatsAsync(category.Id); var latestThread = await _categoryService.GetLatestThreadAsync(category.Id); // Fetch display info for authors var authorIds = new List(); if (latestThread?.Author != null) authorIds.Add(latestThread.Author.Id); if (latestThread?.LastPost?.Author != null) authorIds.Add(latestThread.LastPost.Author.Id); var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync(authorIds.Distinct()); var categoryDto = category.ToDto(threadCount, postCount, latestThread, displayInfoMap); return Ok(categoryDto); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving category {Slug}", slug); return StatusCode(500, new { error = "Failed to retrieve category" }); } } /// /// Create a new forum category /// [HttpPost] [Authorize(Policy = Permissions.Admin.ManageCategories)] public async Task CreateCategory([FromBody] CreateForumCategoryRequest request) { try { var category = await _categoryService.CreateCategoryAsync(request); var (threadCount, postCount) = await _categoryService.GetCategoryStatsAsync(category.Id); var categoryDto = category.ToDto(threadCount, postCount, null, null); return CreatedAtAction(nameof(GetCategory), new { slug = category.Slug }, categoryDto); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Error creating forum category"); return StatusCode(500, new { error = "Failed to create forum category" }); } } /// /// Update an existing forum category /// [HttpPut("{id:guid}")] [Authorize(Policy = Permissions.Admin.ManageCategories)] public async Task UpdateCategory(Guid id, [FromBody] UpdateForumCategoryRequest request) { try { var category = await _categoryService.UpdateCategoryAsync(id, request); if (category == null) { return NotFound(new { error = "Forum category not found" }); } var (threadCount, postCount) = await _categoryService.GetCategoryStatsAsync(category.Id); var latestThread = await _categoryService.GetLatestThreadAsync(category.Id); // Fetch display info for authors var authorIds = new List(); if (latestThread?.Author != null) authorIds.Add(latestThread.Author.Id); if (latestThread?.LastPost?.Author != null) authorIds.Add(latestThread.LastPost.Author.Id); var displayInfoMap = await _userBadgeService.GetUsersDisplayInfoAsync(authorIds.Distinct()); var categoryDto = category.ToDto(threadCount, postCount, latestThread, displayInfoMap); return Ok(categoryDto); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Error updating forum category {Id}", id); return StatusCode(500, new { error = "Failed to update forum category" }); } } /// /// Delete a forum category (only if it has no threads) /// [HttpDelete("{id:guid}")] [Authorize(Policy = Permissions.Admin.ManageCategories)] public async Task DeleteCategory(Guid id) { try { var result = await _categoryService.DeleteCategoryAsync(id); if (!result) { return NotFound(new { error = "Forum category not found" }); } return Ok(new { message = "Forum category deleted successfully" }); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Error deleting forum category {Id}", id); return StatusCode(500, new { error = "Failed to delete forum category" }); } } } }