using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; namespace DramaLing.Api.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class FlashcardsController : ControllerBase { private readonly DramaLingDbContext _context; public FlashcardsController(DramaLingDbContext context) { _context = context; } private Guid GetUserId() { var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value; if (Guid.TryParse(userIdString, out var userId)) return userId; throw new UnauthorizedAccessException("Invalid user ID"); } [HttpGet] public async Task GetFlashcards( [FromQuery] Guid? setId, [FromQuery] string? search, [FromQuery] bool favoritesOnly = false, [FromQuery] int limit = 50, [FromQuery] int offset = 0) { try { var userId = GetUserId(); var query = _context.Flashcards .Include(f => f.CardSet) .Where(f => f.UserId == userId); if (setId.HasValue) query = query.Where(f => f.CardSetId == setId); if (!string.IsNullOrEmpty(search)) query = query.Where(f => f.Word.Contains(search) || f.Translation.Contains(search)); if (favoritesOnly) query = query.Where(f => f.IsFavorite); var total = await query.CountAsync(); var flashcards = await query .OrderByDescending(f => f.CreatedAt) .Skip(offset) .Take(Math.Min(limit, 100)) .Select(f => new { f.Id, f.Word, f.Translation, f.Definition, f.PartOfSpeech, f.Pronunciation, f.Example, f.ExampleTranslation, f.MasteryLevel, f.TimesReviewed, f.IsFavorite, f.NextReviewDate, f.CreatedAt, CardSet = new { f.CardSet.Name, f.CardSet.Color } }) .ToListAsync(); return Ok(new { Success = true, Data = new { Flashcards = flashcards, Total = total, HasMore = offset + limit < total } }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to fetch flashcards", Timestamp = DateTime.UtcNow }); } } [HttpPost] public async Task CreateFlashcard([FromBody] CreateFlashcardRequest request) { try { var userId = GetUserId(); // 驗證卡組是否屬於用戶 var cardSet = await _context.CardSets .FirstOrDefaultAsync(cs => cs.Id == request.CardSetId && cs.UserId == userId); if (cardSet == null) return NotFound(new { Success = false, Error = "Card set not found" }); var flashcard = new Flashcard { Id = Guid.NewGuid(), UserId = userId, CardSetId = request.CardSetId, Word = request.Word.Trim(), Translation = request.Translation.Trim(), Definition = request.Definition.Trim(), PartOfSpeech = request.PartOfSpeech?.Trim(), Pronunciation = request.Pronunciation?.Trim(), Example = request.Example?.Trim(), ExampleTranslation = request.ExampleTranslation?.Trim() }; _context.Flashcards.Add(flashcard); await _context.SaveChangesAsync(); return Ok(new { Success = true, Data = flashcard, Message = "Flashcard created successfully" }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to create flashcard", Timestamp = DateTime.UtcNow }); } } [HttpGet("{id}")] public async Task GetFlashcard(Guid id) { try { var userId = GetUserId(); var flashcard = await _context.Flashcards .Include(f => f.CardSet) .FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId); if (flashcard == null) return NotFound(new { Success = false, Error = "Flashcard not found" }); return Ok(new { Success = true, Data = flashcard }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to fetch flashcard", Timestamp = DateTime.UtcNow }); } } [HttpPut("{id}")] public async Task UpdateFlashcard(Guid id, [FromBody] UpdateFlashcardRequest request) { try { var userId = GetUserId(); var flashcard = await _context.Flashcards .FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId); if (flashcard == null) return NotFound(new { Success = false, Error = "Flashcard not found" }); // 更新欄位 if (!string.IsNullOrEmpty(request.Word)) flashcard.Word = request.Word.Trim(); if (!string.IsNullOrEmpty(request.Translation)) flashcard.Translation = request.Translation.Trim(); if (!string.IsNullOrEmpty(request.Definition)) flashcard.Definition = request.Definition.Trim(); if (request.PartOfSpeech != null) flashcard.PartOfSpeech = request.PartOfSpeech?.Trim(); if (request.Pronunciation != null) flashcard.Pronunciation = request.Pronunciation?.Trim(); if (request.Example != null) flashcard.Example = request.Example?.Trim(); if (request.ExampleTranslation != null) flashcard.ExampleTranslation = request.ExampleTranslation?.Trim(); if (request.IsFavorite.HasValue) flashcard.IsFavorite = request.IsFavorite.Value; flashcard.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); return Ok(new { Success = true, Data = flashcard, Message = "Flashcard updated successfully" }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to update flashcard", Timestamp = DateTime.UtcNow }); } } [HttpDelete("{id}")] public async Task DeleteFlashcard(Guid id) { try { var userId = GetUserId(); var flashcard = await _context.Flashcards .FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId); if (flashcard == null) return NotFound(new { Success = false, Error = "Flashcard not found" }); _context.Flashcards.Remove(flashcard); await _context.SaveChangesAsync(); return Ok(new { Success = true, Message = "Flashcard deleted successfully" }); } catch (UnauthorizedAccessException) { return Unauthorized(new { Success = false, Error = "Unauthorized" }); } catch (Exception ex) { return StatusCode(500, new { Success = false, Error = "Failed to delete flashcard", Timestamp = DateTime.UtcNow }); } } } // DTOs public class CreateFlashcardRequest { public Guid CardSetId { get; set; } public string Word { get; set; } = string.Empty; public string Translation { get; set; } = string.Empty; public string Definition { get; set; } = string.Empty; public string? PartOfSpeech { get; set; } public string? Pronunciation { get; set; } public string? Example { get; set; } public string? ExampleTranslation { get; set; } } public class UpdateFlashcardRequest { public string? Word { get; set; } public string? Translation { get; set; } public string? Definition { get; set; } public string? PartOfSpeech { get; set; } public string? Pronunciation { get; set; } public string? Example { get; set; } public string? ExampleTranslation { get; set; } public bool? IsFavorite { get; set; } }