This commit is contained in:
6d486f49
2026-08-17 01:13:48 -04:00
parent 1d6207fbfe
commit ffaab59585
26 changed files with 3133 additions and 105 deletions
+49 -5
View File
@@ -16,9 +16,9 @@ public class AuthService
_http = http;
_networkStatus = networkStatus;
_jsRuntime = serviceProvider.GetService(typeof(IJSRuntime)) as IJSRuntime;
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 5256
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 7266
? "/graphql"
: "http://localhost:5256/graphql";
: "https://localhost:7266/graphql";
}
public bool IsLoggedIn => !string.IsNullOrEmpty(AuthToken);
@@ -34,6 +34,32 @@ public class AuthService
{
var storedToken = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "chrono_auth_token");
var storedUser = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "chrono_auth_user");
if (string.IsNullOrEmpty(storedToken) || string.IsNullOrEmpty(storedUser))
{
try
{
var cookieStr = await _jsRuntime.InvokeAsync<string?>("eval", "document.cookie");
if (!string.IsNullOrEmpty(cookieStr))
{
foreach (var part in cookieStr.Split(';'))
{
var kv = part.Trim().Split('=');
if (kv.Length == 2)
{
if (kv[0] == "chrono_auth_token" && string.IsNullOrEmpty(storedToken))
storedToken = kv[1];
if (kv[0] == "chrono_auth_user" && string.IsNullOrEmpty(storedUser))
storedUser = kv[1];
}
}
}
}
catch
{
}
}
if (!string.IsNullOrEmpty(storedToken) && !string.IsNullOrEmpty(storedUser))
{
AuthToken = storedToken;
@@ -133,9 +159,12 @@ public class AuthService
if (httpResponse.IsSuccessStatusCode)
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
if (_graphqlEndpoint == "/graphql")
if (_graphqlEndpoint == "/graphql" || _graphqlEndpoint.StartsWith("https://"))
{
var fallbackResponse = await _http.PostAsJsonAsync("http://localhost:5256/graphql", request);
var fallbackUrl = _graphqlEndpoint == "/graphql"
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await _http.PostAsJsonAsync(fallbackUrl, request);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
@@ -144,12 +173,27 @@ public class AuthService
{
try
{
var fallbackResponse = await _http.PostAsJsonAsync("http://localhost:5256/graphql", request);
var fallbackUrl = _graphqlEndpoint.StartsWith("http://")
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await _http.PostAsJsonAsync(fallbackUrl, request);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
if (_graphqlEndpoint == "/graphql")
{
try
{
var fallbackHttp = await _http.PostAsJsonAsync("http://localhost:7266/graphql", request);
if (fallbackHttp.IsSuccessStatusCode)
return await fallbackHttp.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
}
}
}
}
+23 -5
View File
@@ -17,9 +17,9 @@ public class CardNotesGraphQLService
_http = http;
_networkStatus = networkStatus;
_authService = authService;
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 5256
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 7266
? "/graphql"
: "http://localhost:5256/graphql";
: "https://localhost:7266/graphql";
}
public async Task<bool> IsApiOnlineAsync()
@@ -123,9 +123,12 @@ public class CardNotesGraphQLService
if (httpResponse.IsSuccessStatusCode)
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
if (_graphqlEndpoint == "/graphql")
if (_graphqlEndpoint == "/graphql" || _graphqlEndpoint.StartsWith("https://"))
{
var fallbackResponse = await SendToUrlAsync("http://localhost:5256/graphql");
var fallbackUrl = _graphqlEndpoint == "/graphql"
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await SendToUrlAsync(fallbackUrl);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
@@ -134,12 +137,27 @@ public class CardNotesGraphQLService
{
try
{
var fallbackResponse = await SendToUrlAsync("http://localhost:5256/graphql");
var fallbackUrl = _graphqlEndpoint.StartsWith("http://")
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackResponse = await SendToUrlAsync(fallbackUrl);
if (fallbackResponse.IsSuccessStatusCode)
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
if (_graphqlEndpoint == "/graphql")
{
try
{
var fallbackHttp = await SendToUrlAsync("http://localhost:7266/graphql");
if (fallbackHttp.IsSuccessStatusCode)
return await fallbackHttp.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
}
catch
{
}
}
}
}
+352
View File
@@ -0,0 +1,352 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Model;
namespace Web.Services;
public class UserDecksService
{
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
private readonly HttpClient _http;
private readonly AuthService _authService;
private readonly NetworkStatusService _networkStatus;
private readonly string _graphqlEndpoint;
private readonly string _restEndpoint;
public UserDecksService(HttpClient http, AuthService authService, NetworkStatusService networkStatus)
{
_http = http;
_authService = authService;
_networkStatus = networkStatus;
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 7266
? "/graphql"
: "https://localhost:7266/graphql";
_restEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 7266
? "/api/decks"
: "https://localhost:7266/api/decks";
}
public async Task<List<UserDeck>> GetDecksAsync()
{
if (!_networkStatus.IsOnline)
return [];
try
{
var request = new GraphQLRequest
{
Query = @"query GetDecks {
decks {
id
name
cards
divers
notes
season
updatedAt
}
}"
};
var response = await SendGraphQLAsync<GetDecksResponseData>(request);
if (response?.Data?.Decks != null)
return response.Data.Decks;
}
catch (Exception ex)
{
Console.WriteLine($"[WARNING] GraphQL GetDecks failed: {ex.Message}. Trying REST.");
}
// Fallback to REST
try
{
var req = new HttpRequestMessage(HttpMethod.Get, _restEndpoint);
AddAuthHeader(req);
var res = await _http.SendAsync(req);
if (res.IsSuccessStatusCode)
{
var decks = await res.Content.ReadFromJsonAsync<List<UserDeck>>(JsonOptions);
return decks ?? [];
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] REST GetDecks failed: {ex.Message}");
}
return [];
}
public async Task<UserDeck?> GetDeckAsync(Guid id)
{
if (!_networkStatus.IsOnline)
return null;
try
{
var request = new GraphQLRequest
{
Query = @"query GetDeck($id: UUID!) {
deck(id: $id) {
id
name
cards
divers
notes
season
updatedAt
}
}",
Variables = new Dictionary<string, object> { ["id"] = id }
};
var response = await SendGraphQLAsync<GetDeckResponseData>(request);
if (response?.Data?.Deck != null)
return response.Data.Deck;
}
catch (Exception ex)
{
Console.WriteLine($"[WARNING] GraphQL GetDeck failed: {ex.Message}. Trying REST.");
}
// Fallback to REST
try
{
var req = new HttpRequestMessage(HttpMethod.Get, $"{_restEndpoint}/{id}");
AddAuthHeader(req);
var res = await _http.SendAsync(req);
if (res.IsSuccessStatusCode)
{
return await res.Content.ReadFromJsonAsync<UserDeck>(JsonOptions);
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] REST GetDeck failed: {ex.Message}");
}
return null;
}
public async Task<UserDeck?> SaveDeckAsync(UserDeck deck)
{
if (!_networkStatus.IsOnline)
return null;
try
{
var request = new GraphQLRequest
{
Query = @"mutation SaveDeck($input: UserDeckInput!) {
saveDeck(input: $input) {
id
name
cards
divers
notes
season
updatedAt
}
}",
Variables = new Dictionary<string, object>
{
["input"] = new Dictionary<string, object?>
{
["id"] = deck.Id,
["name"] = deck.Name,
["cards"] = deck.Cards,
["divers"] = deck.Divers,
["notes"] = deck.Notes,
["season"] = deck.Season
}
}
};
var response = await SendGraphQLAsync<SaveDeckResponseData>(request);
if (response?.Data?.SaveDeck != null)
return response.Data.SaveDeck;
}
catch (Exception ex)
{
Console.WriteLine($"[WARNING] GraphQL SaveDeck failed: {ex.Message}. Trying REST.");
}
// Fallback to REST
try
{
var req = new HttpRequestMessage(HttpMethod.Post, _restEndpoint)
{
Content = JsonContent.Create(deck)
};
AddAuthHeader(req);
var res = await _http.SendAsync(req);
if (res.IsSuccessStatusCode)
{
return await res.Content.ReadFromJsonAsync<UserDeck>(JsonOptions);
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] REST SaveDeck failed: {ex.Message}");
}
return null;
}
public async Task<bool> DeleteDeckAsync(Guid id)
{
if (!_networkStatus.IsOnline)
return false;
try
{
var request = new GraphQLRequest
{
Query = @"mutation DeleteDeck($id: UUID!) {
deleteDeck(id: $id)
}",
Variables = new Dictionary<string, object> { ["id"] = id }
};
var response = await SendGraphQLAsync<DeleteDeckResponseData>(request);
if (response?.Data?.DeleteDeck == true)
return true;
}
catch (Exception ex)
{
Console.WriteLine($"[WARNING] GraphQL DeleteDeck failed: {ex.Message}. Trying REST.");
}
// Fallback to REST
try
{
var req = new HttpRequestMessage(HttpMethod.Delete, $"{_restEndpoint}/{id}");
AddAuthHeader(req);
var res = await _http.SendAsync(req);
return res.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] REST DeleteDeck failed: {ex.Message}");
}
return false;
}
private void AddAuthHeader(HttpRequestMessage request)
{
if (!string.IsNullOrEmpty(_authService.AuthToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authService.AuthToken);
}
}
private async Task<GraphQLResponse<T>?> SendGraphQLAsync<T>(GraphQLRequest request)
{
var httpRequest = new HttpRequestMessage(HttpMethod.Post, _graphqlEndpoint)
{
Content = JsonContent.Create(request)
};
AddAuthHeader(httpRequest);
try
{
var httpResponse = await _http.SendAsync(httpRequest);
if (httpResponse.IsSuccessStatusCode)
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>(JsonOptions);
if (_graphqlEndpoint == "/graphql" || _graphqlEndpoint.StartsWith("https://"))
{
var fallbackUrl = _graphqlEndpoint == "/graphql"
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackReq = new HttpRequestMessage(HttpMethod.Post, fallbackUrl)
{
Content = JsonContent.Create(request)
};
AddAuthHeader(fallbackReq);
var fallbackRes = await _http.SendAsync(fallbackReq);
if (fallbackRes.IsSuccessStatusCode)
return await fallbackRes.Content.ReadFromJsonAsync<GraphQLResponse<T>>(JsonOptions);
}
}
catch
{
try
{
var fallbackUrl = _graphqlEndpoint.StartsWith("http://")
? "https://localhost:7266/graphql"
: "http://localhost:7266/graphql";
var fallbackReq = new HttpRequestMessage(HttpMethod.Post, fallbackUrl)
{
Content = JsonContent.Create(request)
};
AddAuthHeader(fallbackReq);
var fallbackRes = await _http.SendAsync(fallbackReq);
if (fallbackRes.IsSuccessStatusCode)
return await fallbackRes.Content.ReadFromJsonAsync<GraphQLResponse<T>>(JsonOptions);
}
catch
{
if (_graphqlEndpoint == "/graphql")
{
try
{
var fallbackReq = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7266/graphql")
{
Content = JsonContent.Create(request)
};
AddAuthHeader(fallbackReq);
var fallbackRes = await _http.SendAsync(fallbackReq);
if (fallbackRes.IsSuccessStatusCode)
return await fallbackRes.Content.ReadFromJsonAsync<GraphQLResponse<T>>(JsonOptions);
}
catch
{
}
}
}
}
return null;
}
private class GraphQLRequest
{
[JsonPropertyName("query")] public string Query { get; set; } = string.Empty;
[JsonPropertyName("variables")] public Dictionary<string, object>? Variables { get; set; }
}
private class GraphQLResponse<T>
{
[JsonPropertyName("data")] public T? Data { get; set; }
[JsonPropertyName("errors")] public List<GraphQLError>? Errors { get; set; }
}
private class GraphQLError
{
[JsonPropertyName("message")] public string Message { get; set; } = string.Empty;
}
private class GetDecksResponseData
{
[JsonPropertyName("decks")] public List<UserDeck>? Decks { get; set; }
}
private class GetDeckResponseData
{
[JsonPropertyName("deck")] public UserDeck? Deck { get; set; }
}
private class SaveDeckResponseData
{
[JsonPropertyName("saveDeck")] public UserDeck? SaveDeck { get; set; }
}
private class DeleteDeckResponseData
{
[JsonPropertyName("deleteDeck")] public bool DeleteDeck { get; set; }
}
}