Add system (curated) vs. user-uploaded flashcards
System decks/notes are owned by a reserved sentinel user id, reusing all existing per-user store/index plumbing unchanged. Regular users see them under a separate "Official Decks" section, can study them with fully independent per-user FSRS progress (lazily materialized on first visit), but can never edit or archive them - enforced both in the client (no Edit/Archive controls) and server-side (ownership guard on every mutating endpoint). Includes a small idempotent seed command (`dotnet run -- seed-flashcards`) for testing, since there's no in-app authoring UI yet.
This commit is contained in:
@@ -81,6 +81,44 @@ public sealed class FlashcardScheduleStore
|
||||
}
|
||||
}
|
||||
|
||||
// Sibling of SyncVariantsAsync: materializes this note's variant schedule rows under a
|
||||
// DIFFERENT user's ownership (the studying user, not the note's own owner). Used only for
|
||||
// system notes, so a regular user studying shared content gets independent, per-user FSRS
|
||||
// state. Deliberately duplicated rather than sharing a parameterized private helper with
|
||||
// SyncVariantsAsync, which is called eagerly on every regular note save and must stay
|
||||
// untouched/low-risk.
|
||||
public async Task SyncVariantsForStudyingUserAsync(string studyingUserId, FlashcardNote note)
|
||||
{
|
||||
var desiredVariants = note.Type == FlashcardTypes.Cloze
|
||||
? FlashcardClozeParser.DistinctClozeNumbers(note.Prompt)
|
||||
: [0];
|
||||
if (desiredVariants.Count == 0) return;
|
||||
|
||||
var existing = await GetByNoteIdAsync(studyingUserId, note.Id!);
|
||||
var existingVariants = existing.Select(v => v.Variant).ToHashSet();
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var variant in desiredVariants.Where(v => !existingVariants.Contains(v)))
|
||||
{
|
||||
var schedule = new FlashcardSchedule
|
||||
{
|
||||
UserId = studyingUserId,
|
||||
NoteId = note.Id!,
|
||||
Variant = variant,
|
||||
State = FlashcardScheduleStates.New,
|
||||
DueUtc = now,
|
||||
CreatedUtc = now
|
||||
};
|
||||
try
|
||||
{
|
||||
await _schedules.InsertOneAsync(schedule);
|
||||
}
|
||||
catch (MongoWriteException exception) when (exception.WriteError?.Category == ServerErrorCategory.DuplicateKey)
|
||||
{
|
||||
// Concurrent save already created this variant.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<FlashcardSchedule?> GetAsync(string userId, string cardId) =>
|
||||
await _schedules.Find(v => v.UserId == userId && v.Id == cardId).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user