diff --git a/src/Gameboard.Api.Tests.Unit/Tests/Features/Player/PlayerServiceTestHelpers.cs b/src/Gameboard.Api.Tests.Unit/Tests/Features/Player/PlayerServiceTestHelpers.cs index 8434c719..92f78886 100644 --- a/src/Gameboard.Api.Tests.Unit/Tests/Features/Player/PlayerServiceTestHelpers.cs +++ b/src/Gameboard.Api.Tests.Unit/Tests/Features/Player/PlayerServiceTestHelpers.cs @@ -4,6 +4,7 @@ using Gameboard.Api.Data.Abstractions; using Gameboard.Api.Features.Games; using Gameboard.Api.Features.Practice; +using Gameboard.Api.Features.Scores; using Gameboard.Api.Features.Teams; using Gameboard.Api.Services; using MediatR; @@ -29,6 +30,7 @@ public static PlayerService GetTestableSut INowService? now = null, IPlayerStore? playerStore = null, IPracticeService? practiceService = null, + IScoringService? scoringService = null, IStore? store = null, ISyncStartGameService? syncStartGameService = null, ITeamService? teamService = null @@ -48,6 +50,7 @@ public static PlayerService GetTestableSut now ?? A.Fake(), playerStore ?? A.Fake(), practiceService ?? A.Fake(), + scoringService ?? A.Fake(), store ?? A.Fake(), syncStartGameService ?? A.Fake(), teamService ?? A.Fake() diff --git a/src/Gameboard.Api/Features/Game/GameController.cs b/src/Gameboard.Api/Features/Game/GameController.cs index 3218b74a..802a7d8c 100644 --- a/src/Gameboard.Api/Features/Game/GameController.cs +++ b/src/Gameboard.Api/Features/Game/GameController.cs @@ -240,13 +240,12 @@ public async Task> DeleteImage([FromRoute] string id, [Authorize(AppConstants.AdminPolicy)] public async Task Rerank([FromRoute] string id, CancellationToken cancellationToken) { - AuthorizeAny( - () => Actor.IsDesigner - ); + AuthorizeAny(() => Actor.IsDesigner); await Validate(new Entity { Id = id }); await GameService.ReRank(id); await _scoreDenormalization.DenormalizeGame(id, cancellationToken); + await _mediator.Publish(new GameCacheInvalidateCommand(id)); } } } diff --git a/src/Gameboard.Api/Features/Game/Notifications/GameCacheInvalidateNotification.cs b/src/Gameboard.Api/Features/Game/Notifications/GameCacheInvalidateNotification.cs new file mode 100644 index 00000000..f4b2986b --- /dev/null +++ b/src/Gameboard.Api/Features/Game/Notifications/GameCacheInvalidateNotification.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Gameboard.Api.Features.Games; + +public record GameCacheInvalidateCommand(string GameId) : INotification; diff --git a/src/Gameboard.Api/Features/Hubs/GameHub/GameHubService.cs b/src/Gameboard.Api/Features/Hubs/GameHub/GameHubService.cs index 8e558a91..c2a7ba7a 100644 --- a/src/Gameboard.Api/Features/Hubs/GameHub/GameHubService.cs +++ b/src/Gameboard.Api/Features/Hubs/GameHub/GameHubService.cs @@ -13,7 +13,7 @@ namespace Gameboard.Api.Features.Games; -public interface IGameHubService : INotificationHandler, INotificationHandler +public interface IGameHubService : INotificationHandler, INotificationHandler, INotificationHandler { // invoke functions on clients Task SendExternalGameChallengesDeployStart(GameStartUpdate state); @@ -230,11 +230,14 @@ public async Task Handle(AppStartupNotification appStartupNotification, Cancella .ToArrayAsync(cancellationToken); foreach (var game in games) - await UpdateGameIdUserIdsMap(new GameEnrolledPlayersChangeNotification(new GameEnrolledPlayersChangeContext(game.Id, game.RequireSynchronizedStart))); + await UpdateGameIdUserIdsMap(game.Id); } + public Task Handle(GameCacheInvalidateCommand notification, CancellationToken cancellationToken) + => UpdateGameIdUserIdsMap(notification.GameId); + public Task Handle(GameEnrolledPlayersChangeNotification notification, CancellationToken cancellationToken) - => UpdateGameIdUserIdsMap(notification); + => UpdateGameIdUserIdsMap(notification.Context.GameId); private IEnumerable GetGameUserIds(string gameId) { @@ -245,21 +248,21 @@ private IEnumerable GetGameUserIds(string gameId) return userIds; } - private async Task UpdateGameIdUserIdsMap(GameEnrolledPlayersChangeNotification notification) + private async Task UpdateGameIdUserIdsMap(string gameId) { var gameUsers = await _store .WithNoTracking() .Where(p => p.Game.GameEnd != DateTimeOffset.MinValue || p.Game.GameEnd > _now.Get()) .Where(p => p.Game.PlayerMode == PlayerMode.Competition && p.Mode == PlayerMode.Competition) - .Where(p => p.GameId == notification.Context.GameId) + .Where(p => p.GameId == gameId) .Select(p => p.UserId) .Distinct() .ToArrayAsync(); lock (_gameIdUserIdsMap) { - _gameIdUserIdsMap.TryRemove(notification.Context.GameId, out var existingValue); - _gameIdUserIdsMap.TryAdd(notification.Context.GameId, gameUsers); + _gameIdUserIdsMap.TryRemove(gameId, out var existingValue); + _gameIdUserIdsMap.TryAdd(gameId, gameUsers); } } } diff --git a/src/Gameboard.Api/Features/Player/PlayerService.cs b/src/Gameboard.Api/Features/Player/PlayerService.cs index 0cf4c9d6..3f5d06b5 100644 --- a/src/Gameboard.Api/Features/Player/PlayerService.cs +++ b/src/Gameboard.Api/Features/Player/PlayerService.cs @@ -13,6 +13,7 @@ using Gameboard.Api.Features.Games; using Gameboard.Api.Features.Player; using Gameboard.Api.Features.Practice; +using Gameboard.Api.Features.Scores; using Gameboard.Api.Features.Sponsors; using Gameboard.Api.Features.Teams; using MediatR; @@ -32,6 +33,7 @@ public class PlayerService private readonly IMediator _mediator; private readonly INowService _now; private readonly IPracticeService _practiceService; + private readonly IScoringService _scores; private readonly IStore _store; private readonly ISyncStartGameService _syncStartGameService; private readonly ITeamService _teamService; @@ -56,6 +58,7 @@ public PlayerService( INowService now, IPlayerStore playerStore, IPracticeService practiceService, + IScoringService scores, IStore store, ISyncStartGameService syncStartGameService, ITeamService teamService @@ -75,6 +78,7 @@ ITeamService teamService LocalCache = memCache; _mapper = mapper; PlayerStore = playerStore; + _scores = scores; _store = store; _syncStartGameService = syncStartGameService; _teamService = teamService; @@ -679,10 +683,13 @@ public async Task AdvanceTeams(TeamAdvancement model) foreach (var team in teams) { string newId = _guids.GetGuid(); + // compute complete score, including bonuses + var teamScore = await _scores.GetTeamScore(team.Key, CancellationToken.None); foreach (var player in team) { player.Advanced = true; + var newPlayer = new Data.Player { TeamId = newId, @@ -691,12 +698,12 @@ public async Task AdvanceTeams(TeamAdvancement model) AdvancedFromGameId = player.GameId, AdvancedFromPlayerId = player.Id, AdvancedFromTeamId = player.TeamId, - AdvancedWithScore = model.WithScores ? player.Score : null, + AdvancedWithScore = model.WithScores ? teamScore.OverallScore.TotalScore : null, ApprovedName = player.ApprovedName, Name = player.Name, SponsorId = player.SponsorId, Role = player.Role, - Score = model.WithScores ? player.Score : 0 + Score = model.WithScores ? (int)Math.Floor(teamScore.OverallScore.TotalScore) : 0 }; enrollments.Add(newPlayer);