Skip to content

Commit 11e640d

Browse files
dealloclambstream
andauthored
Working through backlog (#133)
* generate ETag and respect If-Modified-Since see #35 * setup V2 for Dispatch prepares #55 * fix build warnings * insert version in OpenAPI docs on build * add efficient forward-only parsing * update logging * Compile HDML to HTML for all known tags see #55 Co-authored-by: DetKewlDog * fix Chinese translations failing to compile from HDML see #55 * generate "AnyOf" instead of "OneOf" fix #103 * WIP Space station raw endpoint mapping * clean up WIP implementation of space stations see #118 see #120 * fix up mappings for space station see #118 * move space station to V2 * compile V2 HDML see #118 * prevent millisecond drift in wartime fix #130 * fix CI warnings * add flags to Assignment for V1 * map factions in campaigns fix #134 --------- Co-authored-by: TheDarkGlobe <lambstream@gmail.com>
1 parent 5254424 commit 11e640d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+864
-111
lines changed

.github/workflows/fly.yml

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
name: Fly Deploy
22
on:
3-
push:
4-
branches:
5-
- master
3+
release:
4+
types: [ published ]
5+
workflow_dispatch:
6+
inputs:
7+
version:
8+
description: 'Version to use (e.g. v1.2.3)'
9+
required: true
610
jobs:
711
deploy:
812
environment: Fly
@@ -11,9 +15,28 @@ jobs:
1115
concurrency: deploy-group # optional: ensure only one action runs at a time
1216
steps:
1317
- uses: actions/checkout@v4
18+
1419
- uses: superfly/flyctl-actions/setup-flyctl@master
20+
1521
- name: Initial static JSON schema submodule
1622
run: git submodule update --init ./src/Helldivers-2-Models/json
17-
- run: flyctl deploy --remote-only
23+
24+
- name: Set Version
25+
id: set_version
26+
run: |
27+
if [[ -n "${{ github.event.inputs.version }}" ]]; then
28+
VERSION="${{ github.event.inputs.version }}"
29+
else
30+
VERSION="${GITHUB_REF##*/}"
31+
fi
32+
33+
VERSION_WITHOUT_V="${VERSION#v}"
34+
35+
echo "VERSION=$VERSION"
36+
echo "VERSION_WITHOUT_V=$VERSION_WITHOUT_V"
37+
echo "version=$VERSION" >> $GITHUB_OUTPUT
38+
echo "version-without-v=$VERSION_WITHOUT_V" >> $GITHUB_OUTPUT
39+
40+
- run: flyctl deploy --remote-only --build-arg VERSION=$VERSION_WITHOUT_V
1841
env:
1942
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

src/Helldivers-2-API/Controllers/ArrowHeadController.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,18 @@ public static async Task<IResult> Assignments(HttpContext context, ArrowHeadStor
7575

7676
return Results.Bytes(assignments, contentType: "application/json");
7777
}
78+
79+
/// <summary>
80+
/// Fetches THE specific <see cref="SpaceStation" /> (749875195).
81+
/// </summary>
82+
[ProducesResponseType<List<Assignment>>(StatusCodes.Status200OK)]
83+
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
84+
public static async Task<IResult> SpaceStation(HttpContext context, ArrowHeadStore store, [FromRoute] long index)
85+
{
86+
var spaceStation = await store.GetSpaceStation(index, context.RequestAborted);
87+
if (spaceStation is { } bytes)
88+
return Results.Bytes(bytes, contentType: "application/json");
89+
90+
return Results.NotFound();
91+
}
7892
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Helldivers.Core.Contracts.Collections;
2+
using Helldivers.Models.V2;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace Helldivers.API.Controllers.V2;
6+
7+
/// <summary>
8+
/// Contains API endpoints for <see cref="Dispatch" />.
9+
/// </summary>
10+
public static class DispatchController
11+
{
12+
/// <summary>
13+
/// Fetches a list of all available <see cref="Dispatch" /> information available.
14+
/// </summary>
15+
[ProducesResponseType<List<Dispatch>>(StatusCodes.Status200OK)]
16+
public static async Task<IResult> Index(HttpContext context, IStore<Dispatch, int> store)
17+
{
18+
var dispatches = await store.AllAsync(context.RequestAborted);
19+
20+
return Results.Ok(dispatches);
21+
}
22+
23+
24+
/// <summary>
25+
/// Fetches a specific <see cref="Dispatch" /> identified by <paramref name="index" />.
26+
/// </summary>
27+
[ProducesResponseType<Dispatch>(StatusCodes.Status200OK)]
28+
public static async Task<IResult> Show(HttpContext context, IStore<Dispatch, int> store, [FromRoute] int index)
29+
{
30+
var dispatch = await store.GetAsync(index, context.RequestAborted);
31+
if (dispatch is null)
32+
return Results.NotFound();
33+
34+
return Results.Ok(dispatch);
35+
}
36+
}

src/Helldivers-2-API/Controllers/V1/SpaceStationController.cs renamed to src/Helldivers-2-API/Controllers/V2/SpaceStationController.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using Helldivers.Core.Contracts.Collections;
2-
using Helldivers.Models.V1;
2+
using Helldivers.Models.V2;
33
using Microsoft.AspNetCore.Mvc;
44

5-
namespace Helldivers.API.Controllers.V1;
5+
namespace Helldivers.API.Controllers.V2;
66

77
/// <summary>
88
/// Contains API endpoints for <see cref="SpaceStation" />.
@@ -13,8 +13,9 @@ public static class SpaceStationController
1313
/// Fetches a list of all available <see cref="SpaceStation" /> information available.
1414
/// </summary>
1515
[ProducesResponseType<List<SpaceStation>>(StatusCodes.Status200OK)]
16-
public static async Task<IResult> Index(HttpContext context, IStore<SpaceStation, int> store)
16+
public static async Task<IResult> Index(HttpContext context, IStore<SpaceStation, long> store)
1717
{
18+
// TODO: check implementation.
1819
var stations = await store.AllAsync(context.RequestAborted);
1920

2021
return Results.Ok(stations);
@@ -24,7 +25,7 @@ public static async Task<IResult> Index(HttpContext context, IStore<SpaceStation
2425
/// Fetches a specific <see cref="SpaceStation" /> identified by <paramref name="index" />.
2526
/// </summary>
2627
[ProducesResponseType<SpaceStation>(StatusCodes.Status200OK)]
27-
public static async Task<IResult> Show(HttpContext context, IStore<SpaceStation, int> store, [FromRoute] int index)
28+
public static async Task<IResult> Show(HttpContext context, IStore<SpaceStation, long> store, [FromRoute] long index)
2829
{
2930
var station = await store.GetAsync(index, context.RequestAborted);
3031

src/Helldivers-2-API/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ RUN apk add --upgrade --no-cache build-base clang zlib-dev
77
ARG BUILD_CONFIGURATION=Release
88
ARG BUILD_RUNTIME=linux-musl-x64
99
ARG OPENAPI=false
10+
ARG VERSION=0.0.0
1011

1112
WORKDIR /
1213
COPY ./docs/openapi /docs/openapi
@@ -26,15 +27,15 @@ WORKDIR "/src/Helldivers-2-API"
2627
RUN if [[ "${OPENAPI}" = 'true' ]]; \
2728
then \
2829
echo "OPENAPI is set to ${OPENAPI}, running OpenAPI generators" \
29-
&& dotnet build "Helldivers-2-API.csproj" --no-restore -c Debug \
30+
&& dotnet build "Helldivers-2-API.csproj" /p:Version="$VERSION" --no-restore -c Debug \
3031
&& mkdir -p wwwroot \
3132
&& cp /docs/openapi/* wwwroot/ \
3233
; fi
3334
RUN dotnet build "Helldivers-2-API.csproj" --no-restore -r $BUILD_RUNTIME -c $BUILD_CONFIGURATION -o /app/build
3435

3536
FROM build AS publish
3637
ARG BUILD_CONFIGURATION=Release
37-
RUN dotnet publish "Helldivers-2-API.csproj" --no-restore --self-contained -r $BUILD_RUNTIME -c $BUILD_CONFIGURATION -o /app/publish
38+
RUN dotnet publish "Helldivers-2-API.csproj" /p:Version="$VERSION" --no-restore --self-contained -r $BUILD_RUNTIME -c $BUILD_CONFIGURATION -o /app/publish
3839

3940
FROM base AS final
4041
WORKDIR /app
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Helldivers.Sync.Hosted;
2+
using System.Globalization;
3+
4+
namespace Helldivers.API.Middlewares;
5+
6+
/// <summary>
7+
/// Automatically appends the `Etag` header to responses.
8+
/// </summary>
9+
public sealed class EtagMiddleware(ArrowHeadSyncService arrowHead) : IMiddleware
10+
{
11+
/// <inheritdoc />
12+
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
13+
{
14+
if (arrowHead.LastUpdated is { } lastUpdated)
15+
{
16+
var key = $"{lastUpdated.Subtract(DateTime.UnixEpoch).TotalMilliseconds:0}";
17+
context.Response.Headers.ETag = key;
18+
19+
var isModifiedSince = context.Request.Headers.IfModifiedSince.Any(value =>
20+
{
21+
if (DateTime.TryParseExact(value, "R", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal,
22+
out var date))
23+
{
24+
if (date >= lastUpdated)
25+
{
26+
return false;
27+
}
28+
}
29+
30+
return true;
31+
}) || context.Request.Headers.IfModifiedSince.Count == 0;
32+
33+
if (isModifiedSince is false)
34+
{
35+
context.Response.StatusCode = StatusCodes.Status304NotModified;
36+
return;
37+
}
38+
}
39+
40+
await next(context);
41+
}
42+
}

src/Helldivers-2-API/OpenApi/DocumentProcessors/HelldiversDocumentProcessor.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ public void Process(DocumentProcessorContext context)
2828
item.Servers.Clear();
2929
item.Servers.Add(server);
3030
}
31+
32+
foreach (var (_, schema) in context.Document.Components.Schemas)
33+
{
34+
foreach (var (key, property) in schema.Properties)
35+
{
36+
foreach (var oneOf in property.OneOf)
37+
{
38+
property.AnyOf.Add(oneOf);
39+
}
40+
41+
property.OneOf.Clear();
42+
}
43+
}
3144
}
3245
}
3346
#endif

src/Helldivers-2-API/Program.cs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Helldivers.API;
22
using Helldivers.API.Configuration;
33
using Helldivers.API.Controllers;
4-
using Helldivers.API.Controllers.V1;
54
using Helldivers.API.Metrics;
65
using Helldivers.API.Middlewares;
76
using Helldivers.Core.Extensions;
@@ -46,6 +45,7 @@
4645
builder.Services.AddTransient<RateLimitMiddleware>();
4746
builder.Services.AddTransient<RedirectFlyDomainMiddleware>();
4847
builder.Services.AddTransient<BlacklistMiddleware>();
48+
builder.Services.AddTransient<EtagMiddleware>();
4949

5050
// Register the memory cache, used in the rate limiting middleware.
5151
builder.Services.AddMemoryCache();
@@ -115,6 +115,7 @@
115115
options.SerializerOptions.TypeInfoResolverChain.Add(ArrowHeadSerializerContext.Default);
116116
options.SerializerOptions.TypeInfoResolverChain.Add(SteamSerializerContext.Default);
117117
options.SerializerOptions.TypeInfoResolverChain.Add(V1SerializerContext.Default);
118+
options.SerializerOptions.TypeInfoResolverChain.Add(V2SerializerContext.Default);
118119

119120
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
120121
});
@@ -149,10 +150,13 @@
149150
// Only add OpenApi dependencies when generating
150151
if (isRunningAsTool)
151152
{
153+
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0.0";
154+
152155
builder.Services.AddOpenApiDocument(document =>
153156
{
154157
document.Title = "Helldivers 2";
155158
document.Description = "Helldivers 2 Unofficial API";
159+
document.Version = version;
156160

157161
var languages = builder
158162
.Configuration
@@ -172,6 +176,7 @@
172176
document.Description = "An OpenAPI mapping of the official Helldivers API";
173177
document.DocumentName = "arrowhead";
174178
document.ApiGroupNames = ["arrowhead"];
179+
document.Version = version;
175180

176181
document.DocumentProcessors.Add(new Helldivers.API.OpenApi.DocumentProcessors.ArrowHeadDocumentProcessor());
177182
});
@@ -190,6 +195,7 @@
190195

191196
app.UseMiddleware<RedirectFlyDomainMiddleware>();
192197
app.UseMiddleware<BlacklistMiddleware>();
198+
app.UseMiddleware<EtagMiddleware>();
193199

194200
// Track telemetry for Prometheus (Fly.io metrics)
195201
app.UseHttpMetrics(options =>
@@ -231,7 +237,7 @@
231237
.WithTags("dev")
232238
.ExcludeFromDescription();
233239

234-
dev.MapGet("/token", DevelopmentController.CreateToken);
240+
dev.MapGet("/token", Helldivers.API.Controllers.DevelopmentController.CreateToken);
235241

236242
#endif
237243
#endregion
@@ -249,6 +255,7 @@
249255
raw.MapGet("/api/Stats/war/801/summary", ArrowHeadController.Summary);
250256
raw.MapGet("/api/NewsFeed/801", ArrowHeadController.NewsFeed);
251257
raw.MapGet("/api/v2/Assignment/War/801", ArrowHeadController.Assignments);
258+
raw.MapGet("/api/v2/SpaceStation/War/801/{index:long}", ArrowHeadController.SpaceStation);
252259

253260
#endregion
254261

@@ -259,26 +266,38 @@
259266
.WithGroupName("community")
260267
.WithTags("v1");
261268

262-
v1.MapGet("/war", WarController.Show);
269+
v1.MapGet("/war", Helldivers.API.Controllers.V1.WarController.Show);
270+
271+
v1.MapGet("/assignments", Helldivers.API.Controllers.V1.AssignmentsController.Index);
272+
v1.MapGet("/assignments/{index:long}", Helldivers.API.Controllers.V1.AssignmentsController.Show);
263273

264-
v1.MapGet("/assignments", AssignmentsController.Index);
265-
v1.MapGet("/assignments/{index:long}", AssignmentsController.Show);
274+
v1.MapGet("/campaigns", Helldivers.API.Controllers.V1.CampaignsController.Index);
275+
v1.MapGet("/campaigns/{index:int}", Helldivers.API.Controllers.V1.CampaignsController.Show);
266276

267-
v1.MapGet("/campaigns", CampaignsController.Index);
268-
v1.MapGet("/campaigns/{index:int}", CampaignsController.Show);
277+
v1.MapGet("/dispatches", Helldivers.API.Controllers.V1.DispatchController.Index);
278+
v1.MapGet("/dispatches/{index:int}", Helldivers.API.Controllers.V1.DispatchController.Show);
269279

270-
v1.MapGet("/dispatches", DispatchController.Index);
271-
v1.MapGet("/dispatches/{index:int}", DispatchController.Show);
280+
v1.MapGet("/planets", Helldivers.API.Controllers.V1.PlanetController.Index);
281+
v1.MapGet("/planets/{index:int}", Helldivers.API.Controllers.V1.PlanetController.Show);
282+
v1.MapGet("/planet-events", Helldivers.API.Controllers.V1.PlanetController.WithEvents);
272283

273-
v1.MapGet("/planets", PlanetController.Index);
274-
v1.MapGet("/planets/{index:int}", PlanetController.Show);
275-
v1.MapGet("/planet-events", PlanetController.WithEvents);
284+
v1.MapGet("/steam", Helldivers.API.Controllers.V1.SteamController.Index);
285+
v1.MapGet("/steam/{gid}", Helldivers.API.Controllers.V1.SteamController.Show);
286+
287+
#endregion
288+
289+
#region API v2
290+
291+
var v2 = app
292+
.MapGroup("/api/v2")
293+
.WithGroupName("community")
294+
.WithTags("v2");
276295

277-
v1.MapGet("/space-stations", SpaceStationController.Index);
278-
v1.MapGet("/space/stations/{index:int}", SpaceStationController.Show);
296+
v2.MapGet("/dispatches", Helldivers.API.Controllers.V2.DispatchController.Index);
297+
v2.MapGet("/dispatches/{index:int}", Helldivers.API.Controllers.V2.DispatchController.Show);
279298

280-
v1.MapGet("/steam", SteamController.Index);
281-
v1.MapGet("/steam/{gid}", SteamController.Show);
299+
v2.MapGet("/space-stations", Helldivers.API.Controllers.V2.SpaceStationController.Index);
300+
v2.MapGet("/space-stations/{index:long}", Helldivers.API.Controllers.V2.SpaceStationController.Show);
282301

283302
#endregion
284303

src/Helldivers-2-API/appsettings.Development.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
"Helldivers": "Trace",
66
"Microsoft.AspNetCore": "Warning",
77
"System.Net.Http.HttpClient.ApiService": "Warning",
8-
"Microsoft.AspNetCore.Authentication": "Trace"
8+
"Microsoft.AspNetCore.Authentication": "Warning"
99
}
1010
},
1111
"Helldivers": {
1212
"API": {
13+
"RateLimit": 500,
14+
"RateLimitWindow": 10,
1315
"ValidateClients": false,
1416
"Authentication": {
1517
"SigningKey": "I4eGmsXbDXfxlRo5N+w0ToRGN8aWSIaYWbZ2zMFqqnI="

src/Helldivers-2-API/appsettings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"Synchronization": {
2525
"IntervalSeconds": 20,
2626
"DefaultLanguage": "en-US",
27+
"SpaceStations": [
28+
749875195
29+
],
2730
"Languages": [
2831
"en-US",
2932
"de-DE",

0 commit comments

Comments
 (0)