/ GUNRPG.Infrastructure / Backend / OnlineGameBackend.cs
OnlineGameBackend.cs
 1  using System.Net.Http.Json;
 2  using System.Text.Json;
 3  using GUNRPG.Application.Backend;
 4  using GUNRPG.Infrastructure.Persistence;
 5  
 6  namespace GUNRPG.Infrastructure.Backend;
 7  
 8  /// <summary>
 9  /// Online game backend that delegates to the HTTP API.
10  /// Also handles infil snapshot persistence to local storage.
11  /// Combat remains interactive and player-driven via the session-based API — this backend
12  /// handles operator data access, not gameplay execution.
13  /// </summary>
14  public sealed class OnlineGameBackend : IGameBackend
15  {
16      private readonly HttpClient _httpClient;
17      private readonly OfflineStore _offlineStore;
18      private readonly JsonSerializerOptions _jsonOptions;
19  
20      public OnlineGameBackend(HttpClient httpClient, OfflineStore offlineStore, JsonSerializerOptions? jsonOptions = null)
21      {
22          _httpClient = httpClient;
23          _offlineStore = offlineStore;
24          _jsonOptions = jsonOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
25      }
26  
27      /// <inheritdoc />
28      public async Task<OperatorDto?> GetOperatorAsync(string id)
29      {
30          var response = await _httpClient.GetAsync($"operators/{id}");
31          if (!response.IsSuccessStatusCode)
32              return null;
33  
34          var json = await response.Content.ReadAsStringAsync();
35          return MapFromApiJson(id, json);
36      }
37  
38      /// <inheritdoc />
39      public async Task<OperatorDto> InfilOperatorAsync(string id)
40      {
41          // Fetch operator from server
42          var response = await _httpClient.GetAsync($"operators/{id}");
43          if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
44              throw new InvalidOperationException($"Operator {id} not found on server.");
45          response.EnsureSuccessStatusCode();
46  
47          var json = await response.Content.ReadAsStringAsync();
48          var operatorDto = MapFromApiJson(id, json)
49              ?? throw new InvalidOperationException($"Failed to parse operator {id} response from server.");
50  
51          // Persist snapshot locally for offline use
52          _offlineStore.SaveInfiledOperator(operatorDto);
53  
54          Console.WriteLine($"[INFIL] Operator '{operatorDto.Name}' (ID: {id}) infiled successfully.");
55          Console.WriteLine($"[INFIL] Snapshot saved — offline play now available if server becomes unreachable.");
56          return operatorDto;
57      }
58  
59      /// <inheritdoc />
60      public async Task<bool> OperatorExistsAsync(string id)
61      {
62          var response = await _httpClient.GetAsync($"operators/{id}");
63          return response.IsSuccessStatusCode;
64      }
65  
66      public async Task<bool> SyncOfflineMission(OfflineMissionEnvelope envelope, CancellationToken cancellationToken = default)
67      {
68          using var response = await _httpClient.PostAsJsonAsync("operators/offline/sync", envelope, _jsonOptions, cancellationToken);
69          return response.IsSuccessStatusCode;
70      }
71  
72      /// <summary>
73      /// Maps API JSON response to an OperatorDto.
74      /// </summary>
75      private OperatorDto? MapFromApiJson(string id, string json)
76      {
77          try
78          {
79              var dto = JsonSerializer.Deserialize<OperatorDto>(json, _jsonOptions);
80              if (dto != null)
81              {
82                  dto.Id = id;
83              }
84              return dto;
85          }
86          catch
87          {
88              return null;
89          }
90      }
91  }