From c81381af95e0258888c9431f17c81fd6f7636a94 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 17 Feb 2026 15:21:10 -0500 Subject: [PATCH 1/4] [WordService] Refactor RestoreFrontierWords --- Backend.Tests/Mocks/WordRepositoryMock.cs | 9 +++++-- Backend/Interfaces/IWordRepository.cs | 1 + Backend/Repositories/WordRepository.cs | 10 +++++-- Backend/Services/WordService.cs | 33 +++++++++++++++-------- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Backend.Tests/Mocks/WordRepositoryMock.cs b/Backend.Tests/Mocks/WordRepositoryMock.cs index 99d6c450bc..11936493ac 100644 --- a/Backend.Tests/Mocks/WordRepositoryMock.cs +++ b/Backend.Tests/Mocks/WordRepositoryMock.cs @@ -35,8 +35,7 @@ public Task> GetAllWords(string projectId) { try { - var foundWord = _words.Single(word => word.Id == wordId); - return Task.FromResult(foundWord.Clone()); + return Task.FromResult(_words.Single(w => w.ProjectId == projectId && w.Id == wordId).Clone()); } catch (InvalidOperationException) { @@ -44,6 +43,12 @@ public Task> GetAllWords(string projectId) } } + public Task> GetWords(string projectId, List wordIds) + { + return Task.FromResult( + _words.Where(w => w.ProjectId == projectId && wordIds.Contains(w.Id)).Select(w => w.Clone()).ToList()); + } + public Task Create(Word word) { word.Id = Guid.NewGuid().ToString(); diff --git a/Backend/Interfaces/IWordRepository.cs b/Backend/Interfaces/IWordRepository.cs index 0d43c31bf6..1aabed7bbd 100644 --- a/Backend/Interfaces/IWordRepository.cs +++ b/Backend/Interfaces/IWordRepository.cs @@ -8,6 +8,7 @@ public interface IWordRepository { Task> GetAllWords(string projectId); Task GetWord(string projectId, string wordId); + Task> GetWords(string projectId, List wordIds); Task Create(Word word); Task> Create(List words); Task Add(Word word); diff --git a/Backend/Repositories/WordRepository.cs b/Backend/Repositories/WordRepository.cs index e683020bcf..592127f59f 100644 --- a/Backend/Repositories/WordRepository.cs +++ b/Backend/Repositories/WordRepository.cs @@ -80,6 +80,14 @@ public async Task> GetAllWords(string projectId) } } + /// Finds project s with specified ids + public async Task> GetWords(string projectId, List wordIds) + { + using var activity = OtelService.StartActivityWithTag(otelTagName, "getting words"); + + return await _words.Find(GetProjectWordsFilter(projectId, wordIds)).ToListAsync(); + } + /// Removes all s from the WordsCollection and Frontier for specified /// /// A bool: success of operation @@ -130,7 +138,6 @@ private static void PopulateBlankWordTimes(Word word) /// If the Created or Modified time fields are blank, they will automatically calculated using the current /// time. This allows services to set or clear the values before creation to control these fields. /// - /// /// The word created public async Task Create(Word word) { @@ -148,7 +155,6 @@ public async Task Create(Word word) /// If the Created or Modified time fields are blank, they will automatically calculated using the current /// time. This allows services to set or clear the values before creation to control these fields. /// - /// /// The words created public async Task> Create(List words) { diff --git a/Backend/Services/WordService.cs b/Backend/Services/WordService.cs index b225dda1c7..64fa4321ad 100644 --- a/Backend/Services/WordService.cs +++ b/Backend/Services/WordService.cs @@ -93,23 +93,34 @@ private async Task Add(string userId, Word word) return deletedWord.Id; } - /// Restores words to the Frontier - /// A bool: true if successful, false if any don't exist or are already in the Frontier. + /// Restores words to the Frontier that aren't in the Frontier + /// A bool: true if all successfully restored public async Task RestoreFrontierWords(string projectId, List wordIds) { using var activity = OtelService.StartActivityWithTag(otelTagName, "restoring words to Frontier"); - var words = new List(); - foreach (var id in wordIds) + wordIds = wordIds.Distinct().ToList(); + + // Make sure all the words exist but not in the Frontier. + if (await _wordRepo.AreInFrontier(projectId, wordIds, 1)) + { + return false; + } + + var wordsToRestore = await _wordRepo.GetWords(projectId, wordIds); + // Make sure all the words are valid + if (wordsToRestore.Count != wordIds.Count) { - var word = await _wordRepo.GetWord(projectId, id); - if (word is null || await _wordRepo.IsInFrontier(projectId, id)) - { - return false; - } - words.Add(word); + return false; } - await _wordRepo.AddFrontier(words); + if (wordsToRestore.Any(w => w.Accessibility == Status.Deleted)) + { + // We should be restoring words that were removed from the Frontier, + // and not their "Deleted" copies in the words collection. + return false; + } + + await _wordRepo.AddFrontier(wordsToRestore); return true; } From e8be405daeb3091f335f64c1bcbb177269ebe79c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 18 Feb 2026 13:20:18 -0500 Subject: [PATCH 2/4] Fix bunny nitpicks --- Backend/Services/WordService.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Backend/Services/WordService.cs b/Backend/Services/WordService.cs index 64fa4321ad..233408b46c 100644 --- a/Backend/Services/WordService.cs +++ b/Backend/Services/WordService.cs @@ -99,16 +99,22 @@ public async Task RestoreFrontierWords(string projectId, List word { using var activity = OtelService.StartActivityWithTag(otelTagName, "restoring words to Frontier"); + // Allow calls that don't specify any wordIds, but don't do any work. + if (wordIds.Count == 0) + { + return true; + } + wordIds = wordIds.Distinct().ToList(); - // Make sure all the words exist but not in the Frontier. + // Make sure none of the words are in the Frontier. if (await _wordRepo.AreInFrontier(projectId, wordIds, 1)) { return false; } + // Make sure all the words exist and are valid. var wordsToRestore = await _wordRepo.GetWords(projectId, wordIds); - // Make sure all the words are valid if (wordsToRestore.Count != wordIds.Count) { return false; From c1d7a101819466f2cc34c99590293a3a108d1348 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 18 Feb 2026 16:19:18 -0500 Subject: [PATCH 3/4] Make docstring more specific --- Backend/Services/WordService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Backend/Services/WordService.cs b/Backend/Services/WordService.cs index 233408b46c..5d55909e29 100644 --- a/Backend/Services/WordService.cs +++ b/Backend/Services/WordService.cs @@ -94,7 +94,11 @@ private async Task Add(string userId, Word word) } /// Restores words to the Frontier that aren't in the Frontier - /// A bool: true if all successfully restored + /// + /// Aborts if any word can't be restored for any of the following reasons: + /// doesn't exist; has Status.Deleted; or is already in the Frontier + /// + /// A bool: true if all successfully restored; false if none restored. public async Task RestoreFrontierWords(string projectId, List wordIds) { using var activity = OtelService.StartActivityWithTag(otelTagName, "restoring words to Frontier"); From 97711eedefd6061c2beb043596131634b3cd5641 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 18 Feb 2026 17:02:05 -0500 Subject: [PATCH 4/4] Fix summary style --- Backend/Repositories/WordRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Backend/Repositories/WordRepository.cs b/Backend/Repositories/WordRepository.cs index 592127f59f..1492677079 100644 --- a/Backend/Repositories/WordRepository.cs +++ b/Backend/Repositories/WordRepository.cs @@ -88,8 +88,9 @@ public async Task> GetWords(string projectId, List wordIds) return await _words.Find(GetProjectWordsFilter(projectId, wordIds)).ToListAsync(); } - /// Removes all s from the WordsCollection and Frontier for specified - /// + /// + /// Removes all s from the WordsCollection and Frontier for specified + /// /// A bool: success of operation public async Task DeleteAllWords(string projectId) {