using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using QuotifyBE.Data; using QuotifyBE.DTOs; using QuotifyBE.Entities; using QuotifyBE.Mapping; using System.Security.Claims; using Microsoft.EntityFrameworkCore; namespace QuotifyBE.Controllers; [ApiController] [Route("api/v1/quotes")] [Produces("application/json")] public class QuotesController : ControllerBase { private readonly ApplicationDbContext _db; private readonly GeneralUseHelpers guhf; public QuotesController(ApplicationDbContext db, GeneralUseHelpers GUHF) { _db = db; guhf = GUHF; } // GET /api/v1/quotes /// /// Get a page of quotes /// /// A page of quotes consists of 10 quotes or less. If a page does not contain any quotes, 404 is returned. /// The page number /// A page (10 quotes) /// Returned on valid request /// Returned when requested page is invalid or does not exist [HttpGet("page/{page_no}")] [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetQuotePage(int page_no) { var totalQuotes = await _db.Quotes.CountAsync(); const int PageSize = 10; if (page_no <= 0) { return NotFound(new ErrorDTO { Status = "error", error_msg = "Numer strony musi być większy niż 0." }); } var quotes = await _db.Quotes.Include(q => q.QuoteCategories).ThenInclude(qc => qc.Category).Include(q => q.User).Include(q => q.Image).OrderBy(q => q.Id).Skip((page_no-1)*PageSize).Take(PageSize).ToListAsync(); if (quotes == null || totalQuotes == 0) { return NotFound(new ErrorDTO { Status = "error", error_msg = "Brak cytatów na tej stronie." }); } var result = quotes.Select(q => q.ToQuoteShortDTO(_db)).ToList(); //return NotFound(new { status = "error", error_msg = "Not implemented" }); return Ok(result); } // GET /api/v1/quotes/{id} /// /// Get specified quote summary /// /// The quote id in question /// A quote: id, quote content and author, imageUrl and categories if successful, otherwise: error message /// Returned on valid request /// Returned when quote id is invalid or simply doesn't exist [HttpGet("{id}")] [ProducesResponseType(typeof(QuoteShortDTO), 200)] [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetQuoteById(int id) { var quote = await _db.Quotes .Include(q => q.QuoteCategories!) .ThenInclude(qc => qc.Category) .Include(q => q.User) .Include(q => q.Image) .FirstOrDefaultAsync(q => q.Id == id); if (quote == null) return NotFound(new { status = "error", error_msg = "Quote not found" }); return Ok(quote.ToQuoteShortDTO(_db)); } // POST /api/v1/quotes/new /// /// Add a new quote /// /// Newly created quote's id /// Form data containing required quote information /// Returned on valid request /// Returned when any of the categories does not exist /// Returned when user's id does not match the creator's id [HttpPost("new")] [Authorize] [ProducesResponseType(201)] // ? [ProducesResponseType(typeof(ErrorDTO), 400)] [ProducesResponseType(typeof(ErrorDTO), 401)] public async Task CreateQuote([FromBody] CreateQuoteDTO request) { // Get user ID from claims var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userIdClaim == null || !int.TryParse(userIdClaim, out int userId)) return Unauthorized(new {status = "error", error_msg = "Invalid user ID"}); // Find or create image Image? image = null; if (!string.IsNullOrEmpty(request.ImageUrl)) { image = await _db.Images.FirstOrDefaultAsync(i => i.Url == request.ImageUrl); if (image == null) { image = new Image { Url = request.ImageUrl }; _db.Images.Add(image); await _db.SaveChangesAsync(); } } // Create quote var quote = new Quote { Text = request.Text, Author = request.Author, CreatedAt = DateTime.UtcNow, LastUpdatedAt = DateTime.UtcNow, ImageId = image?.Id ?? 0, UserId = userId, QuoteCategories = new List() }; // Attach categories foreach (var categoryId in request.CategoryIds) { var categoryExists = await _db.Categories.AnyAsync(c => c.Id == categoryId); if (!categoryExists) return BadRequest(new {status = "error", error_msg = $"Category ID {categoryId} not found"}); quote.QuoteCategories.Add(new QuoteCategory { CategoryId = categoryId, Quote = quote }); } _db.Quotes.Add(quote); await _db.SaveChangesAsync(); return CreatedAtAction(nameof(GetQuoteById), new { id = quote.Id }, quote); } // GET /api/v1/quotes/random /// /// Get a random quote summary /// /// A quote: id, quote content and author, imageUrl and categories if successful, otherwise: error message /// Returned on valid request /// Returned when no quotes exist [HttpGet("random")] [AllowAnonymous] [ProducesResponseType(typeof(QuoteShortDTO), 200)] [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetRandomQuote() { var totalQuotes = await _db.Quotes.CountAsync(); if (totalQuotes == 0) return NotFound(new { status = "error", error_msg = "No quotes to choose from" }); var random = new Random(); var skip = random.Next(0, totalQuotes); // FIXME var quote = await _db.Quotes .Include(q => q.QuoteCategories!) .ThenInclude(qc => qc.Category) .Skip(skip) .Take(1) .FirstOrDefaultAsync(); if (quote == null) return NotFound(); Image? image = null; if (quote.ImageId != 0) { image = await _db.Images.FirstOrDefaultAsync(i => i.Id == quote.ImageId); } var dto = new QuoteShortDTO { Text = quote.Text, Author = quote.Author, ImageUrl = image?.Url, Categories = quote.QuoteCategories? .Select(qc => qc.Category?.Name ?? "") .Where(name => !string.IsNullOrEmpty(name)) .ToList() ?? new List() }; return Ok(dto); } }