From f4c7c7239fd874cf25f85622b040c8bc2e8cd10e Mon Sep 17 00:00:00 2001 From: Peter Chapman Date: Wed, 1 Apr 2026 13:04:18 +1300 Subject: [PATCH 1/2] Fix duplicate key error when retrieving Paratext users This fixes a regression from SF-2788 Reduce calls to the Paratext Registry (#2529) --- .../Services/ParatextService.cs | 3 ++- .../Services/ParatextServiceTests.cs | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/SIL.XForge.Scripture/Services/ParatextService.cs b/src/SIL.XForge.Scripture/Services/ParatextService.cs index 157c43ac38..711628549e 100644 --- a/src/SIL.XForge.Scripture/Services/ParatextService.cs +++ b/src/SIL.XForge.Scripture/Services/ParatextService.cs @@ -747,7 +747,8 @@ .. JArray Dictionary userMapping = _realtimeService .QuerySnapshots() .Where(u => paratextIds.Contains(u.ParatextId)) - .ToDictionary(u => u.ParatextId, u => u.Id); + .GroupBy(u => u.ParatextId) + .ToDictionary(p => p.Key, p => p.OrderByDescending(u => u.AuthId.Contains("paratext")).First().Id); foreach (ParatextProjectUser user in users) { if (userMapping.TryGetValue(user.ParatextId, out string id)) diff --git a/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs index ad04e962b2..df468d23f8 100644 --- a/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs @@ -6647,6 +6647,7 @@ private class TestEnvironment : IDisposable // User04 and User05 are SF users and is not a PT users. public readonly string User04 = "user04"; public readonly string User05 = "user05"; + public readonly string User06 = "user06"; public readonly string Username01 = "User 01"; public readonly string Username02 = "User 02"; public readonly string Username03 = "User 03"; @@ -7275,18 +7276,31 @@ public void AddUserRepository(User[]? users = null) new User { Id = User01, + AuthId = $"oauth2|paratext|{ParatextUserId01}", ParatextId = ParatextUserId01, Sites = sites, }, new User { Id = User02, + AuthId = $"oauth2|paratext|{ParatextUserId02}", ParatextId = ParatextUserId02, Sites = sites, }, - new User { Id = User03, ParatextId = ParatextUserId03 }, + new User + { + Id = User03, + ParatextId = ParatextUserId03, + AuthId = $"oauth2|paratext|{ParatextUserId03}", + }, new User { Id = User04 }, new User { Id = User05, Sites = sites }, + new User + { + Id = User06, + AuthId = "google-oauth2|123456", + ParatextId = ParatextUserId02, + }, ] ) ); From 5d939f20fc5f8eac37eeef90d0e879bd0830a8dd Mon Sep 17 00:00:00 2001 From: Peter Chapman Date: Wed, 1 Apr 2026 13:38:56 +1300 Subject: [PATCH 2/2] Do not return stack traces in apply draft errors --- .../Services/MachineApiService.cs | 12 ++++++++---- .../Services/MachineApiServiceTests.cs | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/SIL.XForge.Scripture/Services/MachineApiService.cs b/src/SIL.XForge.Scripture/Services/MachineApiService.cs index 51aad7e667..133bbaaaf0 100644 --- a/src/SIL.XForge.Scripture/Services/MachineApiService.cs +++ b/src/SIL.XForge.Scripture/Services/MachineApiService.cs @@ -57,6 +57,7 @@ public class MachineApiService( ISFProjectService projectService, IRealtimeService realtimeService, IOptions servalOptions, + IOptions siteOptions, ISyncService syncService, ITranslationEnginesClient translationEnginesClient, ITranslationEngineTypesClient translationEngineTypesClient, @@ -373,7 +374,8 @@ await draftHubContext.NotifyDraftApplyProgress( BookNum = 0, ChapterNum = 0, Status = DraftApplyStatus.Failed, - Message = result.Log, + Message = + $"An error occurred applying your draft. Please email {siteOptions.Value.IssuesEmail} for help.", } ); @@ -599,7 +601,7 @@ await draftHubContext.NotifyDraftApplyProgress( BookNum = bookNum, ChapterNum = chapterDelta.Number, Status = DraftApplyStatus.Failed, - Message = $"You do not have permission to write to this chapter.", + Message = "You do not have permission to write to this chapter.", } ); continue; @@ -632,7 +634,7 @@ await draftHubContext.NotifyDraftApplyProgress( BookNum = bookNum, ChapterNum = chapterDelta.Number, Status = DraftApplyStatus.Failed, - Message = $"You do not have permission to write to this chapter.", + Message = "You do not have permission to write to this chapter.", } ); continue; @@ -714,7 +716,9 @@ await draftHubContext.NotifyDraftApplyProgress( BookNum = 0, ChapterNum = 0, Status = successful ? DraftApplyStatus.Successful : DraftApplyStatus.Failed, - Message = result.Log, + Message = successful + ? result.Log + : $"An error occurred applying your draft. Please email {siteOptions.Value.IssuesEmail} for help.", } ); diff --git a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs index 28f08f6d86..35ac467509 100644 --- a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs @@ -24,6 +24,7 @@ using Serval.Client; using SIL.Converters.Usj; using SIL.Scripture; +using SIL.XForge.Configuration; using SIL.XForge.DataAccess; using SIL.XForge.EventMetrics; using SIL.XForge.Models; @@ -4979,6 +4980,7 @@ public TestEnvironment() RealtimeService.AddRepository("text_documents", OTType.Json0, TextDocuments); RealtimeService.AddRepository("texts", OTType.RichText, Texts); ServalOptions = Options.Create(new ServalOptions { WebhookSecret = "this_is_a_secret" }); + var siteOptions = Options.Create(new SiteOptions { IssuesEmail = "help@scriptureforge.org" }); SyncService = Substitute.For(); SyncService.SyncAsync(Arg.Any()).Returns(Task.FromResult(HangfireJobId)); TranslationEnginesClient = Substitute.For(); @@ -5019,6 +5021,7 @@ public TestEnvironment() ProjectService, RealtimeService, ServalOptions, + siteOptions, SyncService, TranslationEnginesClient, TranslationEngineTypesClient,