338 lines
10 KiB
C#
338 lines
10 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using DramaLing.Api.Data;
|
|
using DramaLing.Api.Models.Entities;
|
|
|
|
namespace DramaLing.Api.Repositories;
|
|
|
|
/// <summary>
|
|
/// Flashcard Repository 實作,包含所有與詞卡相關的數據存取邏輯
|
|
/// </summary>
|
|
public class FlashcardRepository : BaseRepository<Flashcard>, IFlashcardRepository
|
|
{
|
|
public FlashcardRepository(DramaLingDbContext context, ILogger<FlashcardRepository> logger)
|
|
: base(context, logger)
|
|
{
|
|
}
|
|
|
|
#region 用戶相關查詢
|
|
|
|
public async Task<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<int> 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<int> 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<Dictionary<string, int>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<IEnumerable<Flashcard>> 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<bool> BulkUpdateMasteryLevelAsync(IEnumerable<Guid> 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<bool> BulkUpdateNextReviewDateAsync(IEnumerable<Guid> 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<IEnumerable<Flashcard>> 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
|
|
} |