Skip to content

Commit bbbdacf

Browse files
authored
Word service update return type (#4150)
1 parent 956dd9f commit bbbdacf

6 files changed

Lines changed: 71 additions & 77 deletions

File tree

Backend.Tests/Controllers/LiftControllerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,10 @@ public async Task TestDeletedWordsExportToLift()
413413
// Create untouched word.
414414
await _wordRepo.Create(secondWord);
415415

416-
word.Id = "";
416+
word.Id = wordToUpdate.Id;
417417
word.Vernacular = "updated";
418418

419-
await _wordService.Update(_projId, UserId, wordToUpdate.Id, word);
419+
await _wordService.Update(UserId, word);
420420
await _wordService.DeleteFrontierWord(_projId, UserId, wordToDelete.Id);
421421

422422
_liftService.SetExportInProgress(UserId, ExportId);

Backend.Tests/Services/WordServiceTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public void TestDeleteAudio()
8282
[Test]
8383
public void TestUpdateNotInFrontierNull()
8484
{
85-
Assert.That(_wordService.Update(ProjId, UserId, WordId, new Word()).Result, Is.Null);
85+
Assert.That(_wordService.Update(UserId, new Word() { Id = WordId, ProjectId = ProjId }).Result, Is.Null);
8686
}
8787

8888
[Test]
@@ -92,7 +92,7 @@ public void TestUpdateReplacesFrontierWord()
9292
Assert.That(word, Is.Not.Null);
9393
var oldId = word.Id;
9494
word.Vernacular = "NewVern";
95-
Assert.That(_wordService.Update(ProjId, UserId, oldId, word).Result, Is.EqualTo(word.Id));
95+
Assert.That(_wordService.Update(UserId, word).Result!.Guid, Is.EqualTo(word.Guid));
9696
var frontier = _wordRepo.GetFrontier(ProjId).Result;
9797
Assert.That(frontier, Has.Count.EqualTo(1));
9898
var newWord = frontier.First();
@@ -110,13 +110,13 @@ public void TestUpdateUsingCitationForm()
110110

111111
// Update something other than Vernacular and make sure UsingCitationForm is still true.
112112
word.Note = new() { Text = "change word's note" };
113-
_ = _wordService.Update(ProjId, UserId, word.Id, word).Result;
114-
Assert.That(word.UsingCitationForm, Is.True);
113+
var nonVernUpdate = _wordService.Update(UserId, word).Result;
114+
Assert.That(nonVernUpdate!.UsingCitationForm, Is.True);
115115

116116
// Update the Vernacular and make sure UsingCitationForm is false.
117-
word.Vernacular = "change word's vernacular form";
118-
_ = _wordService.Update(ProjId, UserId, word.Id, word).Result;
119-
Assert.That(word.UsingCitationForm, Is.False);
117+
nonVernUpdate.Vernacular = "change word's vernacular form";
118+
var vernUpdate = _wordService.Update(UserId, nonVernUpdate).Result;
119+
Assert.That(vernUpdate!.UsingCitationForm, Is.False);
120120
}
121121

122122
[Test]

Backend/Controllers/AudioController.cs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,15 @@ namespace BackendFramework.Controllers
1313
[Authorize]
1414
[Produces("application/json")]
1515
[Route("v1/projects/{projectId}/words/{wordId}/audio")]
16-
public class AudioController : Controller
16+
public class AudioController(
17+
IWordRepository wordRepo, IWordService wordService, IPermissionService permissionService) : Controller
1718
{
18-
private readonly IWordRepository _wordRepo;
19-
private readonly IPermissionService _permissionService;
20-
private readonly IWordService _wordService;
19+
private readonly IWordRepository _wordRepo = wordRepo;
20+
private readonly IPermissionService _permissionService = permissionService;
21+
private readonly IWordService _wordService = wordService;
2122

2223
private const string otelTagName = "otel.AudioController";
2324

24-
public AudioController(IWordRepository repo, IWordService wordService, IPermissionService permissionService)
25-
{
26-
_wordRepo = repo;
27-
_permissionService = permissionService;
28-
_wordService = wordService;
29-
}
30-
3125
/// <summary> Gets the audio file in the form of a stream from disk. </summary>
3226
/// <returns> Audio file stream. </returns>
3327
[AllowAnonymous]
@@ -144,9 +138,9 @@ public async Task<IActionResult> UploadAudioFile(
144138
word.Audio.Add(audio);
145139

146140
// Update the word with new audio file
147-
await _wordService.Update(projectId, userId, wordId, word);
141+
string? updatedId = (await _wordService.Update(userId, word))?.Id;
148142

149-
return Ok(word.Id);
143+
return updatedId is null ? NotFound($"wordId: {wordId}") : Ok(updatedId);
150144
}
151145

152146
/// <summary> Deletes audio in <see cref="Word"/> with specified ID </summary>
@@ -177,12 +171,8 @@ public async Task<IActionResult> DeleteAudioFile(string projectId, string wordId
177171
return new UnsupportedMediaTypeResult();
178172
}
179173

180-
var newWord = await _wordService.DeleteAudio(projectId, userId, wordId, fileName);
181-
if (newWord is not null)
182-
{
183-
return Ok(newWord.Id);
184-
}
185-
return NotFound($"wordId: {wordId}; fileName: {fileName}");
174+
string? newId = (await _wordService.DeleteAudio(projectId, userId, wordId, fileName))?.Id;
175+
return newId is null ? NotFound($"wordId: {wordId}; fileName: {fileName}") : Ok(newId);
186176
}
187177
}
188178
}

Backend/Controllers/WordController.cs

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,15 @@ namespace BackendFramework.Controllers
1313
[Authorize]
1414
[Produces("application/json")]
1515
[Route("v1/projects/{projectId}/words")]
16-
public class WordController : Controller
16+
public class WordController(
17+
IWordRepository wordRepo, IWordService wordService, IPermissionService permissionService) : Controller
1718
{
18-
private readonly IWordRepository _wordRepo;
19-
private readonly IPermissionService _permissionService;
20-
private readonly IWordService _wordService;
19+
private readonly IWordRepository _wordRepo = wordRepo;
20+
private readonly IPermissionService _permissionService = permissionService;
21+
private readonly IWordService _wordService = wordService;
2122

2223
private const string otelTagName = "otel.WordController";
2324

24-
public WordController(IWordRepository repo, IWordService wordService, IPermissionService permissionService)
25-
{
26-
_wordRepo = repo;
27-
_permissionService = permissionService;
28-
_wordService = wordService;
29-
}
30-
3125
/// <summary> Deletes specified Frontier <see cref="Word"/>. </summary>
3226
[HttpDelete("frontier/{wordId}", Name = "DeleteFrontierWord")]
3327
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -43,8 +37,8 @@ public async Task<IActionResult> DeleteFrontierWord(string projectId, string wor
4337
}
4438
var userId = _permissionService.GetUserId(HttpContext);
4539

46-
var deletedWordId = await _wordService.DeleteFrontierWord(projectId, userId, wordId);
47-
return deletedWordId is null ? NotFound() : Ok();
40+
var deleted = await _wordService.DeleteFrontierWord(projectId, userId, wordId);
41+
return deleted is null ? NotFound() : Ok();
4842
}
4943

5044
/// <summary> Returns <see cref="Word"/> with specified id. </summary>
@@ -60,12 +54,9 @@ public async Task<IActionResult> GetWord(string projectId, string wordId)
6054
{
6155
return Forbid();
6256
}
63-
var word = await _wordRepo.GetWord(projectId, wordId);
64-
if (word is null)
65-
{
66-
return NotFound();
67-
}
68-
return Ok(word);
57+
58+
Word? word = await _wordRepo.GetWord(projectId, wordId);
59+
return word is null ? NotFound() : Ok(word);
6960
}
7061

7162
/// <summary> Checks if Frontier for specified <see cref="Project"/> has any words. </summary>
@@ -80,6 +71,7 @@ public async Task<IActionResult> HasFrontierWords(string projectId)
8071
{
8172
return Forbid();
8273
}
74+
8375
return Ok(await _wordRepo.HasFrontierWords(projectId));
8476
}
8577

@@ -95,6 +87,7 @@ public async Task<IActionResult> GetFrontierCount(string projectId)
9587
{
9688
return Forbid();
9789
}
90+
9891
return Ok(await _wordRepo.GetFrontierCount(projectId));
9992
}
10093

@@ -110,6 +103,7 @@ public async Task<IActionResult> GetProjectFrontierWords(string projectId)
110103
{
111104
return Forbid();
112105
}
106+
113107
return Ok(await _wordRepo.GetFrontier(projectId));
114108
}
115109

@@ -125,6 +119,7 @@ public async Task<IActionResult> IsInFrontier(string projectId, string wordId)
125119
{
126120
return Forbid();
127121
}
122+
128123
return Ok(await _wordRepo.IsInFrontier(projectId, wordId));
129124
}
130125

@@ -170,7 +165,8 @@ public async Task<IActionResult> GetDuplicateId(string projectId, [FromBody, Bin
170165
}
171166
word.ProjectId = projectId;
172167

173-
return Ok(await _wordService.FindContainingWord(word) ?? "");
168+
string? containingId = await _wordService.FindContainingWord(word);
169+
return Ok(containingId ?? "");
174170
}
175171

176172
/// <summary> Combines a <see cref="Word"/> into the existing duplicate with specified wordId. </summary>
@@ -203,9 +199,8 @@ public async Task<IActionResult> UpdateDuplicate(
203199
return Conflict();
204200
}
205201

206-
await _wordService.Update(duplicatedWord.ProjectId, userId, duplicatedWord.Id, duplicatedWord);
207-
208-
return Ok(duplicatedWord.Id);
202+
string? newId = (await _wordService.Update(userId, duplicatedWord))?.Id;
203+
return newId is null ? NotFound() : Ok(newId);
209204
}
210205

211206
/// <summary> Creates a <see cref="Word"/>. </summary>
@@ -221,9 +216,11 @@ public async Task<IActionResult> CreateWord(string projectId, [FromBody, BindReq
221216
{
222217
return Forbid();
223218
}
219+
224220
word.ProjectId = projectId;
225-
var userId = _permissionService.GetUserId(HttpContext);
226-
return Ok((await _wordService.Create(userId, word)).Id);
221+
222+
string newId = (await _wordService.Create(_permissionService.GetUserId(HttpContext), word)).Id;
223+
return Ok(newId);
227224
}
228225

229226
/// <summary> Updates a <see cref="Word"/>. </summary>
@@ -241,17 +238,13 @@ public async Task<IActionResult> UpdateWord(
241238
{
242239
return Forbid();
243240
}
244-
var document = await _wordRepo.GetWord(projectId, wordId);
245-
if (document is null)
246-
{
247-
return NotFound();
248-
}
249241

250-
// Add the found id to the updated word.
251-
word.Id = document.Id;
252-
var userId = _permissionService.GetUserId(HttpContext);
253-
await _wordService.Update(projectId, userId, wordId, word);
254-
return Ok(word.Id);
242+
// Don't allow changing project or manually setting the Id.
243+
word.ProjectId = projectId;
244+
word.Id = wordId;
245+
246+
string? newId = (await _wordService.Update(_permissionService.GetUserId(HttpContext), word))?.Id;
247+
return newId is null ? NotFound() : Ok(newId);
255248
}
256249

257250
/// <summary> Restore a deleted <see cref="Word"/>. </summary>
@@ -276,7 +269,7 @@ public async Task<IActionResult> RestoreWord(string projectId, string wordId)
276269
return Ok(await _wordService.RestoreFrontierWords(projectId, [wordId]));
277270
}
278271

279-
/// <summary> Revert words from an dictionary of word ids (key: to revert to; value: from frontier). </summary>
272+
/// <summary> Revert words from a dictionary of word ids (key: to revert to; value: from frontier). </summary>
280273
/// <returns> Id dictionary of all words successfully updated (key: was in frontier; value: new id). </returns>
281274
[HttpPost("revertwords", Name = "RevertWords")]
282275
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Dictionary<string, string>))]
@@ -296,11 +289,15 @@ public async Task<IActionResult> RevertWords(
296289
foreach (var kv in wordIds)
297290
{
298291
var idToRevert = kv.Value;
299-
var word = await _wordRepo.GetWord(projectId, kv.Key);
300-
if (word is not null && await _wordRepo.IsInFrontier(projectId, idToRevert))
292+
var priorWord = await _wordRepo.GetWord(projectId, kv.Key);
293+
if (priorWord is not null)
301294
{
302-
await _wordService.Update(projectId, userId, idToRevert, word);
303-
updates[idToRevert] = word.Id;
295+
priorWord.Id = idToRevert;
296+
var newId = (await _wordService.Update(userId, priorWord))?.Id;
297+
if (newId is not null)
298+
{
299+
updates[idToRevert] = newId;
300+
}
304301
}
305302
}
306303
return Ok(updates);

Backend/Interfaces/IWordService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public interface IWordService
88
{
99
Task<Word> Create(string userId, Word word);
1010
Task<List<Word>> Create(string userId, List<Word> words);
11-
Task<string?> Update(string projectId, string userId, string wordId, Word word);
11+
Task<Word?> Update(string userId, Word word);
1212
Task<Word?> DeleteAudio(string projectId, string userId, string wordId, string fileName);
1313
Task<string?> DeleteFrontierWord(string projectId, string userId, string wordId);
1414
Task<bool> RestoreFrontierWords(string projectId, List<string> wordIds);

Backend/Services/WordService.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,33 @@ public async Task<bool> RestoreFrontierWords(string projectId, List<string> word
113113
}
114114

115115
/// <summary> Makes a new word in the Frontier with changes made </summary>
116-
/// <returns> Id of updated word, or null if not found </returns>
117-
public async Task<string?> Update(string projectId, string userId, string wordId, Word word)
116+
/// <returns> Updated word, or null if word-to-update not found </returns>
117+
public async Task<Word?> Update(string userId, Word word)
118118
{
119119
using var activity = OtelService.StartActivityWithTag(otelTagName, "updating a word in Frontier");
120120

121-
// We only want to update words that are in the frontier
122-
var oldWord = await _wordRepo.DeleteFrontier(projectId, wordId);
123-
if (oldWord is null)
121+
var oldWordId = word.Id; // Capture the id in case of changes.
122+
var oldWord = await _wordRepo.GetWord(word.ProjectId, oldWordId);
123+
if (oldWord is null || !await _wordRepo.IsInFrontier(word.ProjectId, oldWordId))
124124
{
125125
return null;
126126
}
127127

128+
word.Created = oldWord.Created;
129+
if (!word.History.Contains(oldWordId))
130+
{
131+
word.History.Add(oldWordId);
132+
}
128133
// If an imported word was using the citation form for its Vernacular,
129134
// only keep UsingCitationForm true if the Vernacular hasn't changed.
130135
word.UsingCitationForm &= word.Vernacular == oldWord.Vernacular;
131136

132-
word.ProjectId = projectId;
133-
word.History.Add(wordId);
137+
var newWord = await Create(userId, word);
138+
139+
// Don't remove the old Frontier word until the new word is successfully created.
140+
await _wordRepo.DeleteFrontier(word.ProjectId, oldWordId);
134141

135-
return (await Create(userId, word)).Id;
142+
return newWord;
136143
}
137144

138145
/// <summary> Checks if a word being added is a duplicate of a preexisting word. </summary>

0 commit comments

Comments
 (0)