diff --git a/src/Gameboard.Api/Extensions/WebApplicationBuilderExtensions.cs b/src/Gameboard.Api/Extensions/WebApplicationBuilderExtensions.cs index 3f8cf1cb..ff5e1237 100644 --- a/src/Gameboard.Api/Extensions/WebApplicationBuilderExtensions.cs +++ b/src/Gameboard.Api/Extensions/WebApplicationBuilderExtensions.cs @@ -68,46 +68,6 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett { var services = builder.Services; - // serilog config - builder.Host.UseSerilog(); - - Serilog.Debugging.SelfLog.Enable(Console.Error); - var loggerConfiguration = new LoggerConfiguration() - .MinimumLevel.Is(settings.Logging.MinimumLogLevel) - .WriteTo.Console(theme: AnsiConsoleTheme.Code); - - // normally, you'd do this in an appsettings.json and just rely on built-in config - // assembly stuff, but we distribute with a helm chart and a weird conf format, so - // we need to manually set up the log levels - foreach (var logNamespace in settings.Logging.NamespacesErrorLevel) - { - loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Error); - } - - foreach (var logNamespace in settings.Logging.NamespacesFatalLevel) - { - loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Fatal); - } - - foreach (var logNamespace in settings.Logging.NamespacesInfoLevel) - { - loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Information); - } - - foreach (var logNamespace in settings.Logging.NamespacesWarningLevel) - { - loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Warning); - } - - // set up sinks on demand - if (settings.Logging.SeqInstanceUrl.IsNotEmpty()) - { - loggerConfiguration = loggerConfiguration.WriteTo.Seq(settings.Logging.SeqInstanceUrl, apiKey: settings.Logging.SeqInstanceApiKey); - } - - // weirdly, this really does appear to be the way to replace the default logger with Serilog 🤷 - Log.Logger = loggerConfiguration.CreateLogger(); - services .AddMvc() .AddGameboardJsonOptions(); @@ -124,6 +84,8 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett .SetApplicationName(AppConstants.DataProtectionPurpose) .PersistKeys(() => settings.Cache); + builder.AddGameboardSerilog(settings); + services .AddSingleton(_ => settings.Core) .AddSingleton(_ => settings.Crucible) @@ -149,4 +111,54 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett services.AddConfiguredAuthentication(settings.Oidc, settings.ApiKey, builder.Environment); services.AddConfiguredAuthorization(); } + + private static WebApplicationBuilder AddGameboardSerilog(this WebApplicationBuilder builder, AppSettings settings) + { + // SERILOG CONFIG + // Gameboard uses Serilog, which is awesome, because Serilog is awesome. By default, it just + // writes to the console sink, which you can ingest with any log ingester (and is useful if you + // choose to monitor the output of its pod in a K8s-style scenario). But if you want richer logging, + // you can add a Seq instance using its configuration so you get nice metadata like the userID + // and name for API requests. Want to use a non-Seq sink? We get it. PR us and let's talk about it. + builder.Host.UseSerilog(); + + Serilog.Debugging.SelfLog.Enable(Console.Error); + var loggerConfiguration = new LoggerConfiguration() + .MinimumLevel.Is(settings.Logging.MinimumLogLevel) + .WriteTo.Console(theme: AnsiConsoleTheme.Code); + + // normally, you'd do this in an appsettings.json and just rely on built-in config + // assembly stuff, but we distribute with a helm chart and a weird conf format, so + // we need to manually set up the log levels + foreach (var logNamespace in settings.Logging.NamespacesErrorLevel) + { + loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Error); + } + + foreach (var logNamespace in settings.Logging.NamespacesFatalLevel) + { + loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Fatal); + } + + foreach (var logNamespace in settings.Logging.NamespacesInfoLevel) + { + loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Information); + } + + foreach (var logNamespace in settings.Logging.NamespacesWarningLevel) + { + loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Warning); + } + + // set up sinks on demand + if (settings.Logging.SeqInstanceUrl.IsNotEmpty()) + { + loggerConfiguration = loggerConfiguration.WriteTo.Seq(settings.Logging.SeqInstanceUrl, apiKey: settings.Logging.SeqInstanceApiKey); + } + + // weirdly, this really does appear to be the way to replace the default logger with Serilog 🤷 + Log.Logger = loggerConfiguration.CreateLogger(); + + return builder; + } } diff --git a/src/Gameboard.Api/Features/Game/GameService.cs b/src/Gameboard.Api/Features/Game/GameService.cs index bb82e490..20ca8f5d 100644 --- a/src/Gameboard.Api/Features/Game/GameService.cs +++ b/src/Gameboard.Api/Features/Game/GameService.cs @@ -29,7 +29,6 @@ public interface IGameService Task SessionForecast(string id); Task Update(ChangedGame account); Task UpdateImage(string id, string type, string filename); - Task UserIsTeamPlayer(string uid, string tid); } public class GameService @@ -38,11 +37,13 @@ public class GameService IMapper mapper, CoreOptions options, Defaults defaults, + IGuidService guids, INowService nowService, IStore store ) : _Service(logger, mapper, options), IGameService { private readonly Defaults _defaults = defaults; + private readonly IGuidService _guids = guids; private readonly INowService _now = nowService; private readonly IStore _store = store; @@ -217,16 +218,6 @@ public async Task UpdateImage(string id, string type, string filename) public Task IsUserPlaying(string gameId, string userId) => _store.AnyAsync(p => p.GameId == gameId && p.UserId == userId, CancellationToken.None); - public async Task UserIsTeamPlayer(string uid, string tid) - { - bool authd = await _store.AnyAsync(u => - u.Id == uid && - u.Enrollments.Any(e => e.TeamId == tid) - , CancellationToken.None); - - return authd; - } - public async Task DeleteGameCardImage(string gameId) { if (!await _store.WithNoTracking().AnyAsync(g => g.Id == gameId)) @@ -248,6 +239,10 @@ public async Task SaveGameCardImage(string gameId, IFormFile file) if (!await _store.WithNoTracking().AnyAsync(g => g.Id == gameId)) throw new ResourceNotFound(gameId); + // we currently intentionally leave the old image around for quasi-logging purposes, + // but we could delete if desired here by getting the path from the Game entity. + // We generate a semi-random name for the new file in GetGameCardFileNameBase to bypass + // network-level caching. var fileName = $"{GetGameCardFileNameBase(gameId)}{Path.GetExtension(file.FileName.ToLower())}"; var path = Path.Combine(Options.ImageFolder, fileName); @@ -259,7 +254,7 @@ public async Task SaveGameCardImage(string gameId, IFormFile file) } private string GetGameCardFileNameBase(string gameId) - => $"{gameId.ToLower()}_card"; + => $"{gameId.ToLower()}_card_{_guids.Generate()[..6]}"; private IQueryable BuildSearchQuery(GameSearchFilter model, bool canViewUnpublished = false) {