Add flashcards feature with spaced repetition scheduling

Adds a full flashcards system: FSRS-based scheduling, cloze/basic
card types, CSV import/export, media storage, and study UI, wired
into navigation and the dashboard alongside existing question flows.
This commit is contained in:
hz
2026-07-21 15:08:52 -05:00
parent 29b89dc404
commit 48a8c8361d
41 changed files with 4458 additions and 8 deletions
@@ -0,0 +1,36 @@
using MongoDB.Driver;
using QuestionsApi.Models;
namespace QuestionsApi.Services.Flashcards;
public sealed class FlashcardSettingsStore
{
private readonly IMongoCollection<FlashcardSettings> _settings;
public FlashcardSettingsStore(MongoDatabaseProvider mongo)
{
_settings = mongo.Database.GetCollection<FlashcardSettings>("FlashcardSettings");
_settings.Indexes.CreateOne(new CreateIndexModel<FlashcardSettings>(
Builders<FlashcardSettings>.IndexKeys.Ascending(v => v.UserId),
new CreateIndexOptions { Unique = true, Name = "flashcard_settings_unique_user" }));
}
public async Task<FlashcardSettings> GetAsync(string userId) =>
await _settings.Find(v => v.UserId == userId).FirstOrDefaultAsync()
?? new FlashcardSettings { UserId = userId };
public async Task<FlashcardSettings> UpdateAsync(string userId, UpdateFlashcardSettingsRequest request)
{
var now = DateTime.UtcNow;
return await _settings.FindOneAndUpdateAsync(
v => v.UserId == userId,
Builders<FlashcardSettings>.Update
.Set(v => v.NewCardsPerDay, Math.Clamp(request.NewCardsPerDay, 0, 2000))
.Set(v => v.ReviewsPerDay, Math.Clamp(request.ReviewsPerDay, 0, 5000))
.Set(v => v.DesiredRetention, FsrsFlashcardScheduler.DesiredRetention)
.Set(v => v.UpdatedUtc, now)
.Inc(v => v.Revision, 1)
.SetOnInsert(v => v.UserId, userId),
new FindOneAndUpdateOptions<FlashcardSettings> { IsUpsert = true, ReturnDocument = ReturnDocument.After });
}
}