using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; namespace DramaLing.Api.Repositories; /// /// Flashcard Repository 實作,包含所有與詞卡相關的數據存取邏輯 /// public class FlashcardRepository : BaseRepository, IFlashcardRepository { public FlashcardRepository(DramaLingDbContext context, ILogger logger) : base(context, logger) { } #region 用戶相關查詢 public async Task> GetFlashcardsByUserIdAsync(Guid userId) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards for user: {UserId}", userId); throw; } } public async Task> GetFlashcardsByCardSetIdAsync(Guid cardSetId) { try { return await _dbSet .AsNoTracking() .Where(f => f.CardSetId == cardSetId && !f.IsArchived) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards for card set: {CardSetId}", cardSetId); throw; } } #endregion #region 學習相關查詢 public async Task> GetDueFlashcardsAsync(Guid userId, DateTime dueDate) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived && f.NextReviewDate <= dueDate && f.MasteryLevel < 5) // 未完全掌握的卡片 .OrderBy(f => f.NextReviewDate) .ThenBy(f => f.EasinessFactor) // 難度較高的優先 .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting due flashcards for user: {UserId}, date: {DueDate}", userId, dueDate); throw; } } public async Task> GetFlashcardsByDifficultyAsync(Guid userId, string difficultyLevel) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived && f.DifficultyLevel == difficultyLevel) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards by difficulty for user: {UserId}, level: {DifficultyLevel}", userId, difficultyLevel); throw; } } public async Task> GetRecentlyAddedAsync(Guid userId, int count) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived) .OrderByDescending(f => f.CreatedAt) .Take(count) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting recently added flashcards for user: {UserId}", userId); throw; } } public async Task> GetMostReviewedAsync(Guid userId, int count) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived) .OrderByDescending(f => f.TimesReviewed) .Take(count) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting most reviewed flashcards for user: {UserId}", userId); throw; } } #endregion #region 統計查詢 public async Task GetTotalFlashcardsCountAsync(Guid userId) { try { return await _dbSet .Where(f => f.UserId == userId && !f.IsArchived) .CountAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting total flashcards count for user: {UserId}", userId); throw; } } public async Task GetMasteredFlashcardsCountAsync(Guid userId) { try { return await _dbSet .Where(f => f.UserId == userId && !f.IsArchived && f.MasteryLevel >= 5) .CountAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting mastered flashcards count for user: {UserId}", userId); throw; } } public async Task> GetFlashcardsByDifficultyStatsAsync(Guid userId) { try { return await _dbSet .Where(f => f.UserId == userId && !f.IsArchived) .GroupBy(f => f.DifficultyLevel) .Select(g => new { Level = g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Level ?? "Unknown", x => x.Count); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards difficulty stats for user: {UserId}", userId); throw; } } #endregion #region 搜尋功能 public async Task> SearchFlashcardsAsync(Guid userId, string searchTerm) { try { var term = searchTerm.ToLower(); return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived && (f.Word.ToLower().Contains(term) || f.Translation.ToLower().Contains(term) || (f.Definition != null && f.Definition.ToLower().Contains(term)) || (f.Example != null && f.Example.ToLower().Contains(term)))) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error searching flashcards for user: {UserId}, term: {SearchTerm}", userId, searchTerm); throw; } } public async Task> GetFavoriteFlashcardsAsync(Guid userId) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && f.IsFavorite && !f.IsArchived) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting favorite flashcards for user: {UserId}", userId); throw; } } public async Task> GetArchivedFlashcardsAsync(Guid userId) { try { return await _dbSet .AsNoTracking() .Where(f => f.UserId == userId && f.IsArchived) .OrderByDescending(f => f.UpdatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting archived flashcards for user: {UserId}", userId); throw; } } #endregion #region 批次操作 public async Task BulkUpdateMasteryLevelAsync(IEnumerable flashcardIds, int newMasteryLevel) { try { var idList = flashcardIds.ToList(); var flashcards = await _dbSet .Where(f => idList.Contains(f.Id)) .ToListAsync(); foreach (var flashcard in flashcards) { flashcard.MasteryLevel = newMasteryLevel; flashcard.UpdatedAt = DateTime.UtcNow; } _dbSet.UpdateRange(flashcards); return true; } catch (Exception ex) { _logger.LogError(ex, "Error bulk updating mastery level for flashcards: {FlashcardIds}", string.Join(",", flashcardIds)); return false; } } public async Task BulkUpdateNextReviewDateAsync(IEnumerable flashcardIds, DateTime newDate) { try { var idList = flashcardIds.ToList(); var flashcards = await _dbSet .Where(f => idList.Contains(f.Id)) .ToListAsync(); foreach (var flashcard in flashcards) { flashcard.NextReviewDate = newDate; flashcard.UpdatedAt = DateTime.UtcNow; } _dbSet.UpdateRange(flashcards); return true; } catch (Exception ex) { _logger.LogError(ex, "Error bulk updating next review date for flashcards: {FlashcardIds}", string.Join(",", flashcardIds)); return false; } } #endregion #region 性能優化查詢 public async Task> GetFlashcardsWithIncludesAsync(Guid userId, bool includeTags = false, bool includeStudyRecords = false) { try { var query = _dbSet.AsNoTracking() .Where(f => f.UserId == userId && !f.IsArchived); if (includeTags) { query = query.Include(f => f.FlashcardTags!) .ThenInclude(ft => ft.Tag); } if (includeStudyRecords) { query = query.Include(f => f.StudyRecords!.OrderByDescending(sr => sr.StudiedAt).Take(10)); } return await query .OrderByDescending(f => f.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error getting flashcards with includes for user: {UserId}", userId); throw; } } #endregion }