diff --git a/src/Gameboard.Api/Extensions/WebApplicationExtensions.cs b/src/Gameboard.Api/Extensions/WebApplicationExtensions.cs index 442ed9e5b..449bb00ae 100644 --- a/src/Gameboard.Api/Extensions/WebApplicationExtensions.cs +++ b/src/Gameboard.Api/Extensions/WebApplicationExtensions.cs @@ -1,8 +1,15 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Gameboard.Api.Common.Services; using Gameboard.Api.Features.Games; using Gameboard.Api.Features.Hubs; using Gameboard.Api.Hubs; +using Gameboard.Api.Services; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Npgsql.Replication; namespace Gameboard.Api.Extensions; @@ -52,4 +59,23 @@ public static WebApplication ConfigureGameboard(this WebApplication app, AppSett return app; } + + public static WebApplication SyncActiveSpecsOnStartup(this WebApplication app, ILogger logger) + { + Task.Run(async () => + { + try + { + using var scope = app.Services.CreateScope(); + var challengeSpecService = scope.ServiceProvider.GetRequiredService(); + await challengeSpecService.SyncActiveSpecs(CancellationToken.None); + } + catch (Exception ex) + { + logger.LogError(message: "Failed to synchronize active challenge specs on startup.", exception: ex); + } + }); + + return app; + } } diff --git a/src/Gameboard.Api/Features/ChallengeSpec/ChallengeSpecService.cs b/src/Gameboard.Api/Features/ChallengeSpec/ChallengeSpecService.cs index d19edf125..73fc0bfb1 100644 --- a/src/Gameboard.Api/Features/ChallengeSpec/ChallengeSpecService.cs +++ b/src/Gameboard.Api/Features/ChallengeSpec/ChallengeSpecService.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using AutoMapper; +using Gameboard.Api.Common.Services; using Gameboard.Api.Data; using Gameboard.Api.Features.GameEngine; using Microsoft.EntityFrameworkCore; @@ -15,6 +16,7 @@ namespace Gameboard.Api.Services; public class ChallengeSpecService : _Service { + private readonly INowService _now; private readonly IStore _store; IGameEngineService GameEngine { get; } @@ -22,11 +24,13 @@ public ChallengeSpecService ( ILogger logger, IMapper mapper, + INowService now, CoreOptions options, IStore store, IGameEngineService gameEngine ) : base(logger, mapper, options) { + _now = now; _store = store; GameEngine = gameEngine; } @@ -83,8 +87,7 @@ public async Task> ListGameSpecs(string gameId) public async Task Sync(string id) { - var externals = (await GameEngine.ListSpecs(new SearchFilter())) - .ToDictionary(o => o.ExternalId); + var externals = await LoadExternalSpecsForSync(); var specs = _store .WithTracking() @@ -95,12 +98,50 @@ public async Task Sync(string id) if (externals.ContainsKey(spec.ExternalId).Equals(false)) continue; - spec.Name = externals[spec.ExternalId].Name; - spec.Description = externals[spec.ExternalId].Description; - spec.Tags = externals[spec.ExternalId].Tags; - spec.Text = externals[spec.ExternalId].Text; + SyncSpec(spec, externals[spec.ExternalId]); } await _store.SaveUpdateRange(specs.ToArray()); } + + /// + /// Updates "active" challenge specs with information from the appropriate + /// game engine (for now, only Topomojo.) + /// + /// "Active" here is defined as specs that are used by a game with a current + /// execution period and + /// + /// + public async Task SyncActiveSpecs(CancellationToken cancellationToken) + { + var nowish = _now.Get(); + var activeSpecs = await _store + .WithTracking() + .Where(s => s.Game.GameEnd > nowish || s.Game.PlayerMode == PlayerMode.Practice) + .ToArrayAsync(cancellationToken); + + var externalSpecs = await LoadExternalSpecsForSync(); + + foreach (var spec in activeSpecs) + { + if (externalSpecs.ContainsKey(spec.ExternalId)) + SyncSpec(spec, externalSpecs[spec.ExternalId]); + } + + await _store.SaveUpdateRange(activeSpecs); + } + + internal async Task> LoadExternalSpecsForSync() + { + return (await GameEngine.ListSpecs(new SearchFilter())) + .ToDictionary(o => o.ExternalId); + } + + internal void SyncSpec(Data.ChallengeSpec spec, ExternalSpec externalSpec) + { + spec.Name = externalSpec.Name; + spec.Description = externalSpec.Description; + spec.Tags = externalSpec.Tags; + spec.Text = externalSpec.Text; + } } diff --git a/src/Gameboard.Api/Features/Game/GameStart/ExternalSyncGameStartService.cs b/src/Gameboard.Api/Features/Game/GameStart/ExternalSyncGameStartService.cs index 73c7173b8..b9e8e9a66 100644 --- a/src/Gameboard.Api/Features/Game/GameStart/ExternalSyncGameStartService.cs +++ b/src/Gameboard.Api/Features/Game/GameStart/ExternalSyncGameStartService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; diff --git a/src/Gameboard.Api/Features/Practice/SearchPracticeChallenges.cs b/src/Gameboard.Api/Features/Practice/SearchPracticeChallenges.cs index f2c519f64..98adb69af 100644 --- a/src/Gameboard.Api/Features/Practice/SearchPracticeChallenges.cs +++ b/src/Gameboard.Api/Features/Practice/SearchPracticeChallenges.cs @@ -70,9 +70,14 @@ public async Task Handle(SearchPracticeChallenge q = q.OrderBy(s => s.Name); var results = await _mapper.ProjectTo(q).ToArrayAsync(cancellationToken); - // fix up relative urls foreach (var result in results) { + // hide tags which aren't in the "suggested searches" configured in the practice area + // (this is because topo has lots of tags that aren't useful to players, so we only) + // want to show them values in the suggested search + result.Tags = result.Tags.Where(t => sluggedSuggestedSearches.Contains(_slugger.Get(t))); + + // fix up relative urls result.Text = _challengeDocsService.ReplaceRelativeUris(result.Text); } diff --git a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs index 0950ec140..90c264f0f 100644 --- a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs +++ b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using AutoMapper; using Gameboard.Api.Data; -using Gameboard.Api.Data.Abstractions; using Gameboard.Api.Features.Games; using Gameboard.Api.Features.Games.External; using Gameboard.Api.Features.Teams; diff --git a/src/Gameboard.Api/Program.cs b/src/Gameboard.Api/Program.cs index 0eefd73bc..21181a2dc 100644 --- a/src/Gameboard.Api/Program.cs +++ b/src/Gameboard.Api/Program.cs @@ -57,7 +57,8 @@ var app = builder.Build(); app .InitializeDatabase(settings, app.Logger) - .ConfigureGameboard(settings); + .ConfigureGameboard(settings) + .SyncActiveSpecsOnStartup(app.Logger); // start! startupLogger.LogInformation("Let the games begin!");