Skip to content

Commit 5a28335

Browse files
2 parents 73d0b8e + c2730c8 commit 5a28335

63 files changed

Lines changed: 1374 additions & 129 deletions

File tree

Some content is hidden

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

.github/workflows/dotnet.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ jobs:
2929
run: dotnet format --no-restore --verify-no-changes --exclude-diagnostics IL2026 IL3050
3030
- name: Check tests
3131
run: dotnet test --no-build --verbosity normal
32+
33+
- name: Upload artifacts
34+
uses: actions/upload-artifact@v4
35+
with:
36+
retention-days: 5
37+
name: openapi-schemas
38+
path: docs/openapi/

.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=${{ steps.set_version.outputs.version-without-v }}
1841
env:
1942
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

.github/workflows/sync.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Validate sync
2+
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
7+
on:
8+
push:
9+
branches: ["master"]
10+
pull_request:
11+
branches: ["master"]
12+
schedule:
13+
- cron: '0 0 * * *'
14+
workflow_dispatch:
15+
16+
jobs:
17+
validate-sync:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Setup .NET
24+
uses: actions/setup-dotnet@v4
25+
with:
26+
dotnet-version: '9.0.x'
27+
28+
- name: Download JSON submodule
29+
run: git submodule update --init ./src/Helldivers-2-Models/json
30+
31+
- name: Run sync and capture logs
32+
id: run_sync
33+
shell: bash
34+
continue-on-error: true
35+
run: |
36+
set -o pipefail
37+
dotnet build
38+
dotnet run --project ./src/Helldivers-2-CI/Helldivers-2-CI.csproj 2>&1 | tee sync.log
39+
40+
- name: Upload artifacts
41+
if: always()
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: sync-artifacts
45+
path: |
46+
v1/*.json
47+
v2/*.json
48+
sync.log
49+
50+
- name: Capture error log
51+
id: sync_log
52+
if: ${{ steps.run_sync.outcome == 'failure' && github.event_name == 'pull_request' }}
53+
run: |
54+
# open a multi-line output called "log"
55+
echo "log<<EOF" >> $GITHUB_OUTPUT
56+
cat sync.log >> $GITHUB_OUTPUT
57+
echo "EOF" >> $GITHUB_OUTPUT
58+
59+
- name: Comment on PR on failure
60+
if: ${{ steps.run_sync.outcome == 'failure' && github.event_name == 'pull_request' }}
61+
uses: peter-evans/create-or-update-comment@v4
62+
with:
63+
token: ${{ secrets.GITHUB_TOKEN }}
64+
issue-number: ${{ github.event.pull_request.number }}
65+
body: |
66+
⚠️ **Sync validation failed** (run #${{ github.run_number }} exited with ${{ steps.run_sync.outcome }})
67+
68+
<details>
69+
<summary>Error log</summary>
70+
71+
```text
72+
${{ steps.sync_log.outputs.log }}
73+
```
74+
</details>
75+
76+
**Artifacts** (JSON + log) here:
77+
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
78+
79+
- name: Fail job on error
80+
if: ${{ steps.run_sync.outcome == 'failure' }}
81+
run: exit 1

Helldivers-2.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{
4141
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
4242
.github\workflows\fly.yml = .github\workflows\fly.yml
4343
.github\workflows\pages.yml = .github\workflows\pages.yml
44+
.github\workflows\sync.yml = .github\workflows\sync.yml
4445
EndProjectSection
4546
EndProject
4647
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{FD5B9284-4689-48BE-B520-633C7269F97C}"
@@ -56,6 +57,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenApi", "OpenApi", "{1A2C
5657
docs\openapi\Helldivers-2-API_arrowhead.json = docs\openapi\Helldivers-2-API_arrowhead.json
5758
EndProjectSection
5859
EndProject
60+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Helldivers-2-CI", "src\Helldivers-2-CI\Helldivers-2-CI.csproj", "{DD942DB1-162A-4915-B10E-C049ED478297}"
61+
EndProject
5962
Global
6063
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6164
Debug|Any CPU = Debug|Any CPU
@@ -82,6 +85,10 @@ Global
8285
{32AE19FB-7D9E-4AC7-A0A5-80DED3680369}.Debug|Any CPU.Build.0 = Debug|Any CPU
8386
{32AE19FB-7D9E-4AC7-A0A5-80DED3680369}.Release|Any CPU.ActiveCfg = Release|Any CPU
8487
{32AE19FB-7D9E-4AC7-A0A5-80DED3680369}.Release|Any CPU.Build.0 = Release|Any CPU
88+
{DD942DB1-162A-4915-B10E-C049ED478297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
89+
{DD942DB1-162A-4915-B10E-C049ED478297}.Debug|Any CPU.Build.0 = Debug|Any CPU
90+
{DD942DB1-162A-4915-B10E-C049ED478297}.Release|Any CPU.ActiveCfg = Release|Any CPU
91+
{DD942DB1-162A-4915-B10E-C049ED478297}.Release|Any CPU.Build.0 = Release|Any CPU
8592
EndGlobalSection
8693
GlobalSection(SolutionProperties) = preSolution
8794
HideSolutionNode = FALSE

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

src/Helldivers-2-API/Helldivers-2-API.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636

3737
<!-- Only include swagger dependencies in DEBUG builds -->
3838
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
39-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
40-
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="9.0.0">
39+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
40+
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="9.0.6">
4141
<PrivateAssets>all</PrivateAssets>
4242
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4343
</PackageReference>
44-
<PackageReference Include="NSwag.AspNetCore" Version="14.1.0" />
44+
<PackageReference Include="NSwag.AspNetCore" Version="14.4.0" />
4545
</ItemGroup>
4646

4747
</Project>
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+
}

0 commit comments

Comments
 (0)