From 4699ed62476273cc655ccf0a225a23613ddde32a Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:21:13 +0200 Subject: [PATCH 1/3] Fixed contests visibility inconsistency bug --- .../ContestsBusinessService.cs | 33 +++++++++++++++---- .../IContestsDataService.cs | 2 +- .../Implementations/ContestsDataService.cs | 11 +++++-- .../Contests/ContestForListingServiceModel.cs | 3 -- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index 31be3e37a0..64687d164e 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -13,6 +13,7 @@ namespace OJS.Services.Ui.Business.Implementations; using OJS.Services.Infrastructure.Constants; using OJS.Services.Infrastructure.Extensions; using OJS.Services.Infrastructure.Models; +using OJS.Services.Infrastructure; using OJS.Services.Ui.Business.Cache; using OJS.Services.Ui.Business.Validations.Implementations.Contests; using OJS.Services.Ui.Data; @@ -34,7 +35,8 @@ public class ContestsBusinessService( IContestParticipantsCacheService contestParticipantsCacheService, IContestsCacheService contestsCacheService, ILecturersInContestsCacheService lecturersInContestsCache, - IContestDetailsValidationService contestDetailsValidationService) + IContestDetailsValidationService contestDetailsValidationService, + IDatesService datesService) : IContestsBusinessService { public async Task> GetContestDetails(int id) @@ -328,6 +330,11 @@ public async Task GetSearchContestsByName( .MapCollection() .ToPagedListAsync(model.PageNumber, model.ItemsPerPage); + foreach (var contest in searchContests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + modelResult.Contests = searchContests; modelResult.TotalContestsCount = allContestsQueryable.Count(); @@ -342,7 +349,7 @@ public async Task> GetAllByFiltersAnd var includeHidden = model.IncludeHidden && user.IsAdmin; var pagedContests = - await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); var participantResultsByContest = new Dictionary>(); if (user.IsAuthenticated) @@ -390,6 +397,11 @@ public async Task> GetParticipatedByU participatedContests, sortAndFilterModel); + foreach (var contest in participatedContestsInPage.Items) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + var participantResultsByContest = new Dictionary>(); var loggedInUser = userProviderService.GetCurrentUser(); @@ -499,10 +511,19 @@ await pagedContests.Items.ForEachAsync(c => .ToListAsync(); public async Task> GetAllParticipatedContests(string username) - => await contestsData - .GetLatestForParticipantByUsername(username) - .MapCollection() - .ToListAsync(); + { + var contests = await contestsData + .GetLatestForParticipantByUsername(username) + .MapCollection() + .ToListAsync(); + + foreach (var contest in contests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); + } + + return contests; + } private static async Task>> MapParticipationResultsToContestsInPage( IQueryable participants) diff --git a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs index ec1c371d88..c4d4fa2e15 100644 --- a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs @@ -18,7 +18,7 @@ Task> ApplyFiltersSortAndPagination( Task> GetAllExpired(); - Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); + Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); IQueryable GetLatestForParticipantByUsername(string username); diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 82bcb38da1..645414594f 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -39,7 +39,7 @@ public async Task> GetAllExpired() public IQueryable GetAllVisible() => this.GetQuery(c => c.IsVisible || c.VisibleFrom <= this.dates.GetUtcNow()); - public async Task> GetAllAsPageByFiltersAndSorting( + public async Task> GetAllAsPageByFiltersAndSorting( ContestFiltersServiceModel model, bool includeHidden = false) { @@ -53,7 +53,14 @@ public async Task> GetAllAsPageByFiltersAndSorting c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - return await this.ApplyFiltersSortAndPagination(contests, model); + var contestsPage = await this.ApplyFiltersSortAndPagination(contests, model); + + foreach (var contest in contestsPage.Items) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= this.dates.GetUtcNow(); + } + + return contestsPage; } public IQueryable GetLatestForParticipantByUsername(string username) diff --git a/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs b/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs index ed630a9ecf..c82be95875 100644 --- a/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs +++ b/Services/UI/OJS.Services.Ui.Models/Contests/ContestForListingServiceModel.cs @@ -84,9 +84,6 @@ public void RegisterMappings(IProfileExpression configuration) .SelectMany(pg => pg.Problems) .Where(x => !x.IsDeleted) .Sum(pr => pr.MaximumPoints))) - .ForMember( - c => c.IsVisible, - opt => opt.MapFrom(s => s.IsVisible || s.VisibleFrom <= DateTime.UtcNow)) // For online contests: // In a problem group with multiple problems, compete points are derived from a single problem, // unlike practice mode where points can be accumulated from all problems across groups. From 8cd41486f464d27070e8f3594adf3ff0b6efb580 Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:31:21 +0200 Subject: [PATCH 2/3] Extracted method --- .../ContestsBusinessService.cs | 26 ++++++++++--------- .../Implementations/ContestsDataService.cs | 9 +------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index 64687d164e..d45ef7f6a5 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -330,10 +330,7 @@ public async Task GetSearchContestsByName( .MapCollection() .ToPagedListAsync(model.PageNumber, model.ItemsPerPage); - foreach (var contest in searchContests) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(searchContests); modelResult.Contests = searchContests; modelResult.TotalContestsCount = allContestsQueryable.Count(); @@ -351,6 +348,8 @@ public async Task> GetAllByFiltersAnd var pagedContests = await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + this.SetContestVisibility(pagedContests.Items); + var participantResultsByContest = new Dictionary>(); if (user.IsAuthenticated) { @@ -397,10 +396,7 @@ public async Task> GetParticipatedByU participatedContests, sortAndFilterModel); - foreach (var contest in participatedContestsInPage.Items) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(participatedContestsInPage.Items); var participantResultsByContest = new Dictionary>(); var loggedInUser = userProviderService.GetCurrentUser(); @@ -517,10 +513,7 @@ public async Task> GetAllParticipated .MapCollection() .ToListAsync(); - foreach (var contest in contests) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= datesService.GetUtcNow(); - } + this.SetContestVisibility(contests); return contests; } @@ -586,4 +579,13 @@ private async Task GetNestedFilterCategoriesIfAny(Co official, isUserAdminOrLecturerInContest); } + + private void SetContestVisibility(IEnumerable contests) + { + var now = datesService.GetUtcNow(); + foreach (var contest in contests) + { + contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= now; + } + } } \ No newline at end of file diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 645414594f..270b9f997a 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -53,14 +53,7 @@ public async Task> GetAllAsPageByFilt .Where(c => c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - var contestsPage = await this.ApplyFiltersSortAndPagination(contests, model); - - foreach (var contest in contestsPage.Items) - { - contest.IsVisible = contest.IsVisible || contest.VisibleFrom <= this.dates.GetUtcNow(); - } - - return contestsPage; + return await this.ApplyFiltersSortAndPagination(contests, model); } public IQueryable GetLatestForParticipantByUsername(string username) From dab1223f8d44d6ca9f8da6931b617a89f33b1516 Mon Sep 17 00:00:00 2001 From: {{name}} <{{email}}> Date: Wed, 25 Mar 2026 23:34:29 +0200 Subject: [PATCH 3/3] Reverts --- .../Implementations/ContestsBusinessService.cs | 2 +- Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs | 2 +- .../Implementations/ContestsDataService.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs index d45ef7f6a5..219400c721 100644 --- a/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs +++ b/Services/UI/OJS.Services.Ui.Business/Implementations/ContestsBusinessService.cs @@ -346,7 +346,7 @@ public async Task> GetAllByFiltersAnd var includeHidden = model.IncludeHidden && user.IsAdmin; var pagedContests = - await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); + await contestsData.GetAllAsPageByFiltersAndSorting(model, includeHidden); this.SetContestVisibility(pagedContests.Items); diff --git a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs index c4d4fa2e15..ec1c371d88 100644 --- a/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/IContestsDataService.cs @@ -18,7 +18,7 @@ Task> ApplyFiltersSortAndPagination( Task> GetAllExpired(); - Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); + Task> GetAllAsPageByFiltersAndSorting(ContestFiltersServiceModel model, bool includeHidden = false); IQueryable GetLatestForParticipantByUsername(string username); diff --git a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs index 270b9f997a..82bcb38da1 100644 --- a/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs +++ b/Services/UI/OJS.Services.Ui.Data/Implementations/ContestsDataService.cs @@ -39,7 +39,7 @@ public async Task> GetAllExpired() public IQueryable GetAllVisible() => this.GetQuery(c => c.IsVisible || c.VisibleFrom <= this.dates.GetUtcNow()); - public async Task> GetAllAsPageByFiltersAndSorting( + public async Task> GetAllAsPageByFiltersAndSorting( ContestFiltersServiceModel model, bool includeHidden = false) { @@ -53,7 +53,7 @@ public async Task> GetAllAsPageByFilt .Where(c => c.CategoryId.HasValue && model.CategoryIds.Contains(c.CategoryId.Value)); } - return await this.ApplyFiltersSortAndPagination(contests, model); + return await this.ApplyFiltersSortAndPagination(contests, model); } public IQueryable GetLatestForParticipantByUsername(string username)