using Microsoft.AspNetCore.Mvc; using DramaLing.Api.Models.Entities; using DramaLing.Api.Models.DTOs; using DramaLing.Api.Repositories; using DramaLing.Api.Services.Review; using Microsoft.AspNetCore.Authorization; using DramaLing.Api.Utils; namespace DramaLing.Api.Controllers; [Route("api/flashcards")] [AllowAnonymous] public class FlashcardsController : BaseController { private readonly IFlashcardRepository _flashcardRepository; private readonly IReviewService _reviewService; public FlashcardsController( IFlashcardRepository flashcardRepository, IReviewService reviewService, ILogger logger) : base(logger) { _flashcardRepository = flashcardRepository; _reviewService = reviewService; } [HttpGet] public async Task GetFlashcards( [FromQuery] string? search = null, [FromQuery] bool favoritesOnly = false) { try { var userId = await GetCurrentUserIdAsync(); var flashcards = await _flashcardRepository.GetByUserIdAsync(userId, search, favoritesOnly); var flashcardData = new { Flashcards = flashcards.Select(f => new { f.Id, f.Word, f.Translation, f.Definition, f.PartOfSpeech, f.Pronunciation, f.Example, f.ExampleTranslation, f.IsFavorite, DifficultyLevelNumeric = f.DifficultyLevelNumeric, CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), f.CreatedAt, f.UpdatedAt, // 添加圖片相關屬性 HasExampleImage = f.FlashcardExampleImages.Any(), PrimaryImageUrl = f.FlashcardExampleImages .Where(fei => fei.IsPrimary) .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") .FirstOrDefault() }), Count = flashcards.Count() }; return SuccessResponse(flashcardData); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards"); return ErrorResponse("INTERNAL_ERROR", "載入詞卡失敗"); } } [HttpPost] public async Task CreateFlashcard([FromBody] CreateFlashcardRequest request) { try { if (!ModelState.IsValid) { return HandleModelStateErrors(); } var userId = await GetCurrentUserIdAsync(); var flashcard = new Flashcard { Id = Guid.NewGuid(), UserId = userId, Word = request.Word, Translation = request.Translation, Definition = request.Definition ?? "", PartOfSpeech = request.PartOfSpeech, Pronunciation = request.Pronunciation, Example = request.Example, ExampleTranslation = request.ExampleTranslation, DifficultyLevelNumeric = CEFRHelper.ToNumeric(request.CEFR ?? "A0"), CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; await _flashcardRepository.AddAsync(flashcard); await _flashcardRepository.SaveChangesAsync(); return SuccessResponse(flashcard, "詞卡創建成功"); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error creating flashcard"); return ErrorResponse("INTERNAL_ERROR", "創建詞卡失敗"); } } [HttpGet("{id}")] public async Task GetFlashcard(Guid id) { try { var userId = await GetCurrentUserIdAsync(); var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id); if (flashcard == null) { return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404); } // 格式化返回數據,保持與列表 API 一致 var flashcardData = new { flashcard.Id, flashcard.Word, flashcard.Translation, flashcard.Definition, flashcard.PartOfSpeech, flashcard.Pronunciation, flashcard.Example, flashcard.ExampleTranslation, flashcard.IsFavorite, DifficultyLevelNumeric = flashcard.DifficultyLevelNumeric, CEFR = CEFRHelper.ToString(flashcard.DifficultyLevelNumeric), flashcard.CreatedAt, flashcard.UpdatedAt, // 添加圖片相關屬性 HasExampleImage = flashcard.FlashcardExampleImages.Any(), PrimaryImageUrl = flashcard.FlashcardExampleImages .Where(fei => fei.IsPrimary) .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") .FirstOrDefault(), // 添加複習相關屬性 (暫時預設值) TimesReviewed = 0, MasteryLevel = 0, NextReviewDate = (DateTime?)null, // 保留完整的圖片關聯數據供前端使用 FlashcardExampleImages = flashcard.FlashcardExampleImages }; return SuccessResponse(flashcardData); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcard {FlashcardId}", id); return ErrorResponse("INTERNAL_ERROR", "取得詞卡失敗"); } } [HttpPut("{id}")] public async Task UpdateFlashcard(Guid id, [FromBody] CreateFlashcardRequest request) { try { if (!ModelState.IsValid) { return HandleModelStateErrors(); } var userId = await GetCurrentUserIdAsync(); var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id); if (flashcard == null) { return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404); } // 更新詞卡資訊 flashcard.Word = request.Word; flashcard.Translation = request.Translation; flashcard.Definition = request.Definition ?? ""; flashcard.PartOfSpeech = request.PartOfSpeech; flashcard.Pronunciation = request.Pronunciation; flashcard.Example = request.Example; flashcard.ExampleTranslation = request.ExampleTranslation; flashcard.UpdatedAt = DateTime.UtcNow; await _flashcardRepository.UpdateAsync(flashcard); await _flashcardRepository.SaveChangesAsync(); return SuccessResponse(flashcard, "詞卡更新成功"); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error updating flashcard {FlashcardId}", id); return ErrorResponse("INTERNAL_ERROR", "更新詞卡失敗"); } } [HttpDelete("{id}")] public async Task DeleteFlashcard(Guid id) { try { var userId = await GetCurrentUserIdAsync(); var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id); if (flashcard == null) { return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404); } await _flashcardRepository.DeleteAsync(flashcard); await _flashcardRepository.SaveChangesAsync(); return SuccessResponse(new { Id = id }, "詞卡已刪除"); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error deleting flashcard {FlashcardId}", id); return ErrorResponse("INTERNAL_ERROR", "刪除詞卡失敗"); } } [HttpPost("{id}/favorite")] public async Task ToggleFavorite(Guid id) { try { var userId = await GetCurrentUserIdAsync(); var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id); if (flashcard == null) { return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404); } flashcard.IsFavorite = !flashcard.IsFavorite; flashcard.UpdatedAt = DateTime.UtcNow; await _flashcardRepository.UpdateAsync(flashcard); await _flashcardRepository.SaveChangesAsync(); var result = new { Id = flashcard.Id, IsFavorite = flashcard.IsFavorite }; var message = flashcard.IsFavorite ? "已加入收藏" : "已取消收藏"; return SuccessResponse(result, message); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error toggling favorite for flashcard {FlashcardId}", id); return ErrorResponse("INTERNAL_ERROR", "切換收藏狀態失敗"); } } [HttpGet("due")] public async Task GetDueFlashcards( [FromQuery] int limit = 10, [FromQuery] bool includeToday = true, [FromQuery] bool includeOverdue = true, [FromQuery] bool favoritesOnly = false) { try { var userId = await GetCurrentUserIdAsync(); var query = new DueFlashcardsQuery { Limit = limit, IncludeToday = includeToday, IncludeOverdue = includeOverdue, FavoritesOnly = favoritesOnly }; var response = await _reviewService.GetDueFlashcardsAsync(userId, query); return Ok(response); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error getting due flashcards"); return ErrorResponse("INTERNAL_ERROR", "載入待複習詞卡失敗"); } } [HttpPost("{id}/review")] public async Task SubmitReview(Guid id, [FromBody] ReviewRequest request) { try { if (!ModelState.IsValid) { return HandleModelStateErrors(); } var userId = await GetCurrentUserIdAsync(); var response = await _reviewService.SubmitReviewAsync(userId, id, request); return Ok(response); } catch (UnauthorizedAccessException) { return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401); } catch (Exception ex) { _logger.LogError(ex, "Error submitting review for flashcard {FlashcardId}", id); return ErrorResponse("INTERNAL_ERROR", "提交複習結果失敗"); } } } // DTO 類別 public class CreateFlashcardRequest { 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; } = string.Empty; public string Pronunciation { get; set; } = string.Empty; public string Example { get; set; } = string.Empty; public string? ExampleTranslation { get; set; } public string? CEFR { get; set; } = string.Empty; }