diff --git a/backend/DramaLing.Api/Controllers/FlashcardsController.cs b/backend/DramaLing.Api/Controllers/FlashcardsController.cs index 4bbc287..2f93bbb 100644 --- a/backend/DramaLing.Api/Controllers/FlashcardsController.cs +++ b/backend/DramaLing.Api/Controllers/FlashcardsController.cs @@ -6,6 +6,7 @@ using DramaLing.Api.Contracts.Services.Review; using Microsoft.AspNetCore.Authorization; using DramaLing.Api.Utils; using DramaLing.Api.Services; +using DramaLing.Api.Services.Storage; using DramaLing.Api.Data; using Microsoft.EntityFrameworkCore; @@ -18,17 +19,33 @@ public class FlashcardsController : BaseController private readonly IFlashcardRepository _flashcardRepository; private readonly IReviewService _reviewService; private readonly DramaLingDbContext _context; + private readonly IImageStorageService _imageStorageService; public FlashcardsController( IFlashcardRepository flashcardRepository, IReviewService reviewService, DramaLingDbContext context, + IImageStorageService imageStorageService, IAuthService authService, ILogger logger) : base(logger, authService) { _flashcardRepository = flashcardRepository; _reviewService = reviewService; _context = context; + _imageStorageService = imageStorageService; + } + + private async Task GetImageUrlAsync(string? relativePath) + { + if (string.IsNullOrEmpty(relativePath)) + return null; + + // 確保路徑包含 examples/ 前綴 + var fullPath = relativePath.StartsWith("examples/") + ? relativePath + : $"examples/{relativePath}"; + + return await _imageStorageService.GetImageUrlAsync(fullPath); } [HttpGet] @@ -47,38 +64,50 @@ public class FlashcardsController : BaseController .Where(fr => fr.UserId == userId && flashcardIds.Contains(fr.FlashcardId)) .ToDictionaryAsync(fr => fr.FlashcardId); + // 重構為 foreach 迴圈,支援異步 URL 處理 + var flashcardList = new List(); + + foreach (var f in flashcards) + { + reviews.TryGetValue(f.Id, out var review); + + // 取得主要圖片的相對路徑並轉換為完整 URL + var primaryImageRelativePath = f.FlashcardExampleImages + .Where(fei => fei.IsPrimary) + .Select(fei => fei.ExampleImage.RelativePath) + .FirstOrDefault(); + + var primaryImageUrl = await GetImageUrlAsync(primaryImageRelativePath); + + flashcardList.Add(new + { + f.Id, + f.Word, + f.Translation, + f.Definition, + f.PartOfSpeech, + f.Pronunciation, + f.Example, + f.ExampleTranslation, + f.IsFavorite, + f.Synonyms, + DifficultyLevelNumeric = f.DifficultyLevelNumeric, + CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), + f.CreatedAt, + f.UpdatedAt, + // 添加複習相關屬性 + NextReviewDate = review?.NextReviewDate ?? DateTime.UtcNow.AddDays(1), + TimesReviewed = review?.TotalCorrectCount + review?.TotalWrongCount + review?.TotalSkipCount ?? 0, + MasteryLevel = review?.SuccessCount ?? 0, + // 添加圖片相關屬性 + HasExampleImage = f.FlashcardExampleImages.Any(), + PrimaryImageUrl = primaryImageUrl + }); + } + var flashcardData = new { - Flashcards = flashcards.Select(f => { - reviews.TryGetValue(f.Id, out var review); - return new - { - f.Id, - f.Word, - f.Translation, - f.Definition, - f.PartOfSpeech, - f.Pronunciation, - f.Example, - f.ExampleTranslation, - f.IsFavorite, - f.Synonyms, - DifficultyLevelNumeric = f.DifficultyLevelNumeric, - CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), - f.CreatedAt, - f.UpdatedAt, - // 添加複習相關屬性 - NextReviewDate = review?.NextReviewDate ?? DateTime.UtcNow.AddDays(1), - TimesReviewed = review?.TotalCorrectCount + review?.TotalWrongCount + review?.TotalSkipCount ?? 0, - MasteryLevel = review?.SuccessCount ?? 0, - // 添加圖片相關屬性 - HasExampleImage = f.FlashcardExampleImages.Any(), - PrimaryImageUrl = f.FlashcardExampleImages - .Where(fei => fei.IsPrimary) - .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") - .FirstOrDefault() - }; - }), + Flashcards = flashcardList, Count = flashcards.Count() }; @@ -181,10 +210,10 @@ public class FlashcardsController : BaseController MasteryLevel = review?.SuccessCount ?? 0, // 添加圖片相關屬性 HasExampleImage = flashcard.FlashcardExampleImages.Any(), - PrimaryImageUrl = flashcard.FlashcardExampleImages + PrimaryImageUrl = await GetImageUrlAsync(flashcard.FlashcardExampleImages .Where(fei => fei.IsPrimary) - .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") - .FirstOrDefault(), + .Select(fei => fei.ExampleImage.RelativePath) + .FirstOrDefault()), // 保留完整的圖片關聯數據供前端使用 FlashcardExampleImages = flashcard.FlashcardExampleImages }; @@ -407,4 +436,4 @@ public class CreateFlashcardRequest public string? ExampleTranslation { get; set; } public string? Synonyms { get; set; } // AI 生成的同義詞 (JSON 字串) public string? CEFR { get; set; } = string.Empty; -} \ No newline at end of file +}