Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Gameboard.Api/Common/Services/HtmlToImageService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace Gameboard.Api.Common.Services;

Expand All @@ -19,7 +20,7 @@ public interface IHtmlToImageService
Task<byte[]> ToPng(string fileName, string htmlString, int? width = null, int? height = null);
}

internal class HtmlToImageService(CoreOptions coreOptions) : IHtmlToImageService
internal class HtmlToImageService(CoreOptions coreOptions, ILogger<HtmlToImageService> logger) : IHtmlToImageService
{

public async Task<byte[]> ToPdf(string fileName, string htmlString, int? width = null, int? height = null)
Expand All @@ -32,8 +33,6 @@ public async Task<byte[]> ToPdf(string fileName, string htmlString, int? width =
{
"--title",
""" "Gameboard Certificate" '""",
// "--dpi",
// "300",
"--margin-top",
"0mm",
"--margin-right",
Expand All @@ -52,6 +51,7 @@ public async Task<byte[]> ToPdf(string fileName, string htmlString, int? width =
};

// run chromium and verify
logger.LogInformation("Starting wkhtmltopdf with args {args}", string.Join(' ', args));
var result = await StartProcessAsync.StartAsync("wkhtmltopdf", args);
if (result != 0)
throw new Exception("PDF generation failed.");
Expand Down Expand Up @@ -102,6 +102,7 @@ private async Task<ToTempImageResult> ToTempImage(string fileName, string htmlSt
tempImagePath
};

logger.LogInformation("Starting wkhtmltopdf with args {args}", string.Join(' ', args));
var result = await StartProcessAsync.StartAsync("wkhtmltoimage", args);
if (result != 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@
namespace Gameboard.Api.Features.Admin;

public sealed record GetAppActiveChallengesQuery(PlayerMode PlayerMode) : IRequest<GetAppActiveChallengesResponse>;
public sealed record GetAppActiveChallengesResponse
(
IEnumerable<AppActiveChallengeSpec> Specs
);
public sealed record GetAppActiveChallengesResponse(IEnumerable<AppActiveChallengeSpec> Specs);

internal class GetAppActiveChallengesHandler(
internal class GetAppActiveChallengesHandler
(
IAppService appOverviewService,
IStore store,
ITeamService teamService,
IValidatorService validatorService
) : IRequestHandler<GetAppActiveChallengesQuery, GetAppActiveChallengesResponse>
) : IRequestHandler<GetAppActiveChallengesQuery, GetAppActiveChallengesResponse>
{
private readonly IAppService _appOverviewService = appOverviewService;
private readonly IStore _store = store;
Expand Down Expand Up @@ -52,7 +50,7 @@ await _validatorService
var specIds = challenges.Select(c => c.SpecId).Distinct().ToArray();
var teamIds = challenges.Select(c => c.TeamId).Distinct().ToArray();

// get specs separately because _ugh_
// get specs separately because _ugh_ #317
var specs = await _store
.WithNoTracking<Data.ChallengeSpec>()
.Include(s => s.Game)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ await validator
// distinct specIds implied by all three and add them to the group
var specIds = new List<string>();

// specific specs
if (request.Request.AddBySpecIds.IsNotEmpty())
{
specIds.AddRange(request.Request.AddBySpecIds);
}

// by game id
if (request.Request.AddByGameId.IsNotEmpty())
{
var query = await practiceService.GetPracticeChallengesQueryBase(includeHiddenChallengesIfHasPermission: false);
Expand All @@ -69,6 +71,33 @@ await validator
specIds.AddRange(specIdsFromGame);
}

// by various game metadata
var addByDivision = request.Request.AddByGameDivision.IsEmpty() ? null : request.Request.AddByGameDivision.ToLower();
var addBySeason = request.Request.AddByGameSeason.IsEmpty() ? null : request.Request.AddByGameSeason.ToLower();
var addBySeries = request.Request.AddByGameSeries.IsEmpty() ? null : request.Request.AddByGameSeries.ToLower();
var addByTrack = request.Request.AddByGameTrack.IsEmpty() ? null : request.Request.AddByGameTrack.ToLower();

if (addByDivision != null || addBySeason != null || addBySeries != null || addByTrack != null)
{
var query = await practiceService.GetPracticeChallengesQueryBase(includeHiddenChallengesIfHasPermission: false);
var specIdsFromGameMetaData = await query
.Where
(
s => s.Game != null &&
(
(addByDivision == null || s.Game.Division.ToLower() == addByDivision) &&
(addBySeason == null || s.Game.Season.ToLower() == addBySeason) &&
(addBySeries == null || s.Game.Competition.ToLower() == addBySeries) &&
(addByTrack == null || s.Game.Track.ToLower() == addByTrack)
)
)
.Select(s => s.Id)
.ToArrayAsync(cancellationToken);

specIds.AddRange(specIdsFromGameMetaData);
}

// by challenge tag
if (request.Request.AddByTag.IsNotEmpty())
{
var query = await practiceService.GetPracticeChallengesQueryBase(includeHiddenChallengesIfHasPermission: false);
Expand Down Expand Up @@ -103,18 +132,6 @@ await validator
// have to do this with proper EF because we don't have a real entity for the many-to-many
var specs = finalSpecIds.Select(sId => new PracticeChallengeGroupChallengeSpec { PracticeChallengeGroupId = request.ChallengeGroupId, ChallengeSpecId = sId }).ToArray();
await store.SaveAddRange(specs);
// await store.DoTransaction(async ctx =>
// {
// var group = await ctx.PracticeChallengeGroups.FindAsync(request.ChallengeGroupId, cancellationToken);

// foreach (var spec in specs)
// {
// ctx.Attach(spec);
// group.ChallengeSpecs.Add(spec);
// }

// await ctx.SaveChangesAsync(cancellationToken);
// }, cancellationToken);

return new AddChallengesToGroupResponse { AddedChallengeSpecIds = finalSpecIds };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ namespace Gameboard.Api.Features.Practice;
public sealed class AddChallengesToGroupRequest
{
public string? AddByGameId { get; set; }
public string? AddByGameDivision { get; set; }
public string? AddByGameSeason { get; set; }
public string? AddByGameSeries { get; set; }
public string? AddByGameTrack { get; set; }
public string[]? AddBySpecIds { get; set; }
public string? AddByTag { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task<PagedEnumerable<SiteUsageReportChallenge>> Handle(GetSiteUsage
{
await _validator.Validate(request, cancellationToken);

// have to do dumb joiny stuff because challengespec ARGH
// have to do dumb joiny stuff because challengespec ARGH #317
var specChallenges = await _reportService
.GetBaseQuery(request.ReportParameters)
.GroupBy(c => c.SpecId)
Expand Down
2 changes: 1 addition & 1 deletion src/Gameboard.Api/Features/Scores/ScoringService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public async Task<TeamScore> GetTeamScore(string teamId, CancellationToken cance
// hidden specs don't count toward score
var specs = await _store
.WithNoTracking<Data.ChallengeSpec>()
.Include(s => s.Bonuses)
.Include(s => s.Bonuses)
.Where(spec => spec.GameId == captain.GameId)
.Where(spec => !spec.IsHidden)
.ToListAsync(cancellationToken);
Expand Down
117 changes: 61 additions & 56 deletions src/Gameboard.Api/Structure/HttpClientStartupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,69 +19,74 @@ public static IServiceCollection AddConfiguredHttpClients(
CoreOptions config
)
{
services.AddHttpClient<ITopoMojoApiClient, TopoMojoApiClient>()
.ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri(config.GameEngineUrl);
client.DefaultRequestHeaders.Add("x-api-key", config.GameEngineClientSecret);
client.DefaultRequestHeaders.Add("x-api-client", config.GameEngineClientName);
client.Timeout = TimeSpan.FromSeconds(300);
})
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);
services
.AddHttpClient<ITopoMojoApiClient, TopoMojoApiClient>()
.ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri(config.GameEngineUrl);
client.DefaultRequestHeaders.Add("x-api-key", config.GameEngineClientSecret);
client.DefaultRequestHeaders.Add("x-api-client", config.GameEngineClientName);
client.Timeout = TimeSpan.FromSeconds(300);
})
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);

services.AddHttpClient("topo", client =>
{
client.BaseAddress = new Uri(config.GameEngineUrl);
client.DefaultRequestHeaders.Add("x-api-key", config.GameEngineClientSecret);
client.DefaultRequestHeaders.Add("x-api-client", config.GameEngineClientName);
client.Timeout = TimeSpan.FromSeconds(300);
})
.AddPolicyHandler
(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);
services
.AddHttpClient("topo", client =>
{
client.BaseAddress = new Uri(config.GameEngineUrl);
client.DefaultRequestHeaders.Add("x-api-key", config.GameEngineClientSecret);
client.DefaultRequestHeaders.Add("x-api-client", config.GameEngineClientName);
client.Timeout = TimeSpan.FromSeconds(300);
})
.AddPolicyHandler
(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);

services.AddHttpClient("identity", client =>
{
// Workaround to avoid TaskCanceledException after several retries. TODO: find a better way to handle this.
client.Timeout = Timeout.InfiniteTimeSpan;
})
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);
services
.AddHttpClient("identity", client =>
{
// Workaround to avoid TaskCanceledException after several retries. TODO: find a better way to handle this.
client.Timeout = Timeout.InfiniteTimeSpan;
})
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);

services.AddHttpClient("alloy", client =>
{
// Workaround to avoid TaskCanceledException after several retries. TODO: find a better way to handle this.
client.Timeout = Timeout.InfiniteTimeSpan;
})
.AddHttpMessageHandler<AuthenticatingHandler>()
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);
services
.AddHttpClient("alloy", client =>
{
// Workaround to avoid TaskCanceledException after several retries. TODO: find a better way to handle this.
client.Timeout = Timeout.InfiniteTimeSpan;
})
.AddHttpMessageHandler<AuthenticatingHandler>()
.AddPolicyHandler(
HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(config.GameEngineMaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
);

services.AddScoped<IAlloyApiClient, AlloyApiClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var settings = p.GetRequiredService<CrucibleOptions>();
services
.AddScoped<IAlloyApiClient, AlloyApiClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var settings = p.GetRequiredService<CrucibleOptions>();

var uri = new Uri(settings.ApiUrl);
var uri = new Uri(settings.ApiUrl);

var httpClient = httpClientFactory.CreateClient("alloy");
httpClient.BaseAddress = uri;
var httpClient = httpClientFactory.CreateClient("alloy");
httpClient.BaseAddress = uri;

return new AlloyApiClient(httpClient);
});
return new AlloyApiClient(httpClient);
});

services.AddTransient<AuthenticatingHandler>();

Expand Down