11using BUTR . NexusModsStats . Options ;
22using BUTR . NexusModsStats . Services ;
3+ using BUTR . NexusModsStats . Utils ;
34
5+ using Microsoft . Extensions . Caching . Distributed ;
46using Microsoft . Extensions . DependencyInjection . Extensions ;
57using Microsoft . Extensions . Diagnostics . HealthChecks ;
68using Microsoft . Extensions . Options ;
@@ -14,26 +16,24 @@ public static partial class UptimeKumaExtensions
1416{
1517 public static WebApplicationBuilder AddUptimeKuma ( this WebApplicationBuilder builder )
1618 {
17- var assemblyName = typeof ( DownloadsExtensions ) . Assembly . GetName ( ) ;
18- var userAgent = $ "{ assemblyName . Name ?? "ERROR" } v{ assemblyName . Version ? . ToString ( ) ?? "ERROR" } (github.com/BUTR)";
19-
2019 builder . Services . TryAddEnumerable ( ServiceDescriptor . Singleton < IEndpointDefinition , UptimeKumaEndpointDefinition > ( ) ) ;
2120 builder . Services . AddHttpClient < IHealthCheckPublisher , UptimeKumaHealthCheckPublisher > ( ) . ConfigureHttpClient ( ( sp , client ) =>
2221 {
2322 var options = sp . GetRequiredService < IOptions < UptimeKumaOptions > > ( ) . Value ;
2423
2524 if ( Uri . TryCreate ( options . Endpoint , UriKind . Absolute , out var uri ) )
2625 client . BaseAddress = uri ;
27- client . DefaultRequestHeaders . Add ( "User-Agent" , userAgent ) ;
26+ client . DefaultRequestHeaders . Add ( "User-Agent" , HttpUtils . UserAgent ) ;
2827 } ) ;
2928 builder . Services . AddHttpClient < NexusModsApiHealthCheck > ( ) . ConfigureHttpClient ( ( _ , client ) =>
3029 {
3130 client . BaseAddress = new Uri ( "https://nexusmods.statuspage.io/" ) ;
32- client . DefaultRequestHeaders . Add ( "User-Agent" , userAgent ) ;
31+ client . DefaultRequestHeaders . Add ( "User-Agent" , HttpUtils . UserAgent ) ;
3332 client . Timeout = TimeSpan . FromSeconds ( 3 ) ;
3433 } ) ;
3534 builder . Services . AddHealthChecks ( )
36- . AddCheck < NexusModsApiHealthCheck > ( "NexusModsApi" ) ;
35+ . AddCheck < NexusModsApiHealthCheck > ( "NexusModsApi" )
36+ . AddCheck < DistributedCacheHealthCheck > ( "Cache" ) ;
3737
3838 return builder ;
3939 }
@@ -74,39 +74,45 @@ public void RegisterEndpoints(WebApplication app)
7474 }
7575 }
7676
77+ public sealed class DistributedCacheHealthCheck : IHealthCheck
78+ {
79+ private static readonly DistributedCacheEntryOptions Expiration = new ( ) { AbsoluteExpirationRelativeToNow = TimeSpan . FromMinutes ( 1 ) } ;
80+
81+ private readonly IDistributedCache _cache ;
82+
83+ public DistributedCacheHealthCheck ( IDistributedCache cache )
84+ {
85+ _cache = cache ;
86+ }
87+
88+ public async Task < HealthCheckResult > CheckHealthAsync ( HealthCheckContext context , CancellationToken ct = default )
89+ {
90+ try
91+ {
92+ await _cache . SetStringAsync ( "healthz" , "ok" , Expiration , ct ) ;
93+ return HealthCheckResult . Healthy ( ) ;
94+ }
95+ catch ( Exception e )
96+ {
97+ return HealthCheckResult . Unhealthy ( "Distributed cache is not available" , e ) ;
98+ }
99+ }
100+ }
101+
77102 public partial class NexusModsApiHealthCheck : IHealthCheck
78103 {
79104 [ JsonSerializable ( typeof ( Components ) ) ]
80- [ JsonSourceGenerationOptions ( PropertyNamingPolicy = JsonKnownNamingPolicy . CamelCase ) ]
81105 public partial class NexusModsStatusJsonSerializerContext : JsonSerializerContext ;
82106
107+ // Only the fields that are actually used are declared - extra JSON properties are ignored,
108+ // and loosely-typed members (e.g. 'object group_id') would break AOT serialization
83109 public record Component (
84- [ property: JsonPropertyName ( "id" ) ] string id ,
85- [ property: JsonPropertyName ( "name" ) ] string name ,
86- [ property: JsonPropertyName ( "status" ) ] string status ,
87- [ property: JsonPropertyName ( "created_at" ) ] DateTime created_at ,
88- [ property: JsonPropertyName ( "updated_at" ) ] DateTime updated_at ,
89- [ property: JsonPropertyName ( "position" ) ] int position ,
90- [ property: JsonPropertyName ( "description" ) ] string description ,
91- [ property: JsonPropertyName ( "showcase" ) ] bool showcase ,
92- [ property: JsonPropertyName ( "start_date" ) ] string start_date ,
93- [ property: JsonPropertyName ( "group_id" ) ] object group_id ,
94- [ property: JsonPropertyName ( "page_id" ) ] string page_id ,
95- [ property: JsonPropertyName ( "group" ) ] bool group ,
96- [ property: JsonPropertyName ( "only_show_if_degraded" ) ] bool only_show_if_degraded
97- ) ;
98-
99- public record Page(
100- [ property: JsonPropertyName ( "id" ) ] string id ,
101- [ property: JsonPropertyName ( "name" ) ] string name ,
102- [ property: JsonPropertyName ( "url" ) ] string url ,
103- [ property: JsonPropertyName ( "time_zone" ) ] string time_zone ,
104- [ property: JsonPropertyName ( "updated_at" ) ] DateTime updated_at
110+ [ property: JsonPropertyName ( "name" ) ] string Name ,
111+ [ property: JsonPropertyName ( "status" ) ] string Status
105112 ) ;
106113
107114 public record Components (
108- [ property: JsonPropertyName ( "page" ) ] Page page ,
109- [ property: JsonPropertyName ( "components" ) ] IReadOnlyList < Component > components
115+ [ property: JsonPropertyName ( "components" ) ] IReadOnlyList < Component > ComponentList
110116 ) ;
111117
112118 private readonly HttpClient _httpClient ;
@@ -118,15 +124,22 @@ public NexusModsApiHealthCheck(HttpClient httpClient)
118124
119125 public async Task < HealthCheckResult > CheckHealthAsync ( HealthCheckContext context , CancellationToken ct = default )
120126 {
121- var response = await _httpClient . GetFromJsonAsync ( "api/v2/components.json" , NexusModsStatusJsonSerializerContext . Default . Components , ct ) ;
122- var apiComponent = response ? . components . FirstOrDefault ( x => x . name == "API" ) ;
123- if ( apiComponent is null )
124- return HealthCheckResult . Unhealthy ( "NexusMods API Status not available" ) ;
127+ try
128+ {
129+ var response = await _httpClient . GetFromJsonAsync ( "api/v2/components.json" , NexusModsStatusJsonSerializerContext . Default . Components , ct ) ;
130+ var apiComponent = response ? . ComponentList . FirstOrDefault ( x => x . Name == "API" ) ;
131+ if ( apiComponent is null )
132+ return HealthCheckResult . Unhealthy ( "NexusMods API Status not available" ) ;
125133
126- if ( apiComponent . status != "operational" )
127- return HealthCheckResult . Degraded ( $ "NexusMods API Status is { apiComponent . status } ") ;
134+ if ( apiComponent . Status != "operational" )
135+ return HealthCheckResult . Degraded ( $ "NexusMods API Status is { apiComponent . Status } ") ;
128136
129- return HealthCheckResult . Healthy ( ) ;
137+ return HealthCheckResult . Healthy ( ) ;
138+ }
139+ catch ( Exception e ) when ( e is not OperationCanceledException || ! ct . IsCancellationRequested )
140+ {
141+ return HealthCheckResult . Unhealthy ( "NexusMods API Status not available" , e ) ;
142+ }
130143 }
131144 }
132145}
0 commit comments