using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nuuru.Server.DTOs.Booru; using Nuuru.Server.Extensions; using Nuuru.Server.Services; namespace Nuuru.Server.Controllers { [ApiController] [Route("api/booru/tags")] [AllowAnonymous] public class TagController : ControllerBase { private readonly ITagService _tagService; private readonly ILogger _logger; public TagController(ITagService tagService, ILogger logger) { _tagService = tagService; _logger = logger; } /// /// Get all tags ordered by post count /// [HttpGet] public async Task GetAllTags() { try { var tags = await _tagService.GetAllTagsAsync(); var tagDtos = tags.ToDto(); return Ok(tagDtos); } catch (Exception ex) { _logger.LogError(ex, "Error getting all tags"); return StatusCode(500, new { error = "Failed to retrieve tags" }); } } /// /// Get a specific tag by ID /// [HttpGet("{id:guid}")] public async Task GetTagById(Guid id) { try { var tag = await _tagService.GetTagByIdAsync(id); if (tag == null) { return NotFound(new { error = "Tag not found" }); } var tagDto = tag.ToDto(); return Ok(tagDto); } catch (Exception ex) { _logger.LogError(ex, "Error getting tag {TagId}", id); return StatusCode(500, new { error = "Failed to retrieve tag" }); } } /// /// Get a tag by name /// [HttpGet("name/{name}")] public async Task GetTagByName(string name) { try { var tag = await _tagService.GetTagByNameAsync(name); if (tag == null) { return NotFound(new { error = "Tag not found" }); } var tagDto = tag.ToDto(); return Ok(tagDto); } catch (Exception ex) { _logger.LogError(ex, "Error getting tag {TagName}", name); return StatusCode(500, new { error = "Failed to retrieve tag" }); } } /// /// Get tags in chunks for incremental loading (ordered by post count) /// [HttpGet("chunk")] public async Task GetTagsChunk([FromQuery] int offset = 0, [FromQuery] int limit = 500) { try { if (offset < 0) { return BadRequest(new { error = "Offset must be non-negative" }); } if (limit < 1 || limit > 1000) { return BadRequest(new { error = "Limit must be between 1 and 1000" }); } var (tags, totalCount) = await _tagService.GetTagsChunkAsync(offset, limit); var tagDtos = tags.ToDto(); return Ok(new { tags = tagDtos, totalCount, offset, limit, hasMore = offset + tags.Count() < totalCount }); } catch (Exception ex) { _logger.LogError(ex, "Error getting tags chunk at offset {Offset}", offset); return StatusCode(500, new { error = "Failed to retrieve tags" }); } } /// /// Get popular tags ordered by post count /// [HttpGet("popular")] public async Task GetPopularTags([FromQuery] int count = 20) { try { if (count < 1 || count > 100) { return BadRequest(new { error = "Count must be between 1 and 100" }); } var tags = await _tagService.GetPopularTagsAsync(count); var tagDtos = tags.ToDto(); return Ok(tagDtos); } catch (Exception ex) { _logger.LogError(ex, "Error getting popular tags"); return StatusCode(500, new { error = "Failed to retrieve popular tags" }); } } /// /// Get tags created or updated since a given timestamp (for incremental sync) /// [HttpGet("since")] public async Task GetTagsSince([FromQuery] DateTime since) { try { var tags = await _tagService.GetTagsChangedSinceAsync(since); var tagDtos = tags.ToDto(); return Ok(new { tags = tagDtos, count = tagDtos.Count, queriedSince = since }); } catch (Exception ex) { _logger.LogError(ex, "Error getting tags created since {Since}", since); return StatusCode(500, new { error = "Failed to retrieve tags" }); } } /// /// Search for tags by query string /// [HttpGet("search")] public async Task SearchTags([FromQuery] string query, [FromQuery] int limit = 10) { try { if (string.IsNullOrWhiteSpace(query)) { return BadRequest(new { error = "Query parameter is required" }); } if (query.Length > 500) { return BadRequest(new { error = "Search query must not exceed 500 characters" }); } if (limit < 1 || limit > 100) { return BadRequest(new { error = "Limit must be between 1 and 100" }); } var tags = await _tagService.SearchTagsAsync(query, limit); var tagDtos = tags.ToDto(); return Ok(tagDtos); } catch (Exception ex) { _logger.LogError(ex, "Error searching tags with query {Query}", query); return StatusCode(500, new { error = "Failed to search tags" }); } } } }