API Test
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Web.Services;
|
||||
|
||||
public class AuthService
|
||||
{
|
||||
private readonly string _graphqlEndpoint;
|
||||
private readonly HttpClient _http;
|
||||
private readonly IJSRuntime? _jsRuntime;
|
||||
private readonly NetworkStatusService _networkStatus;
|
||||
|
||||
public AuthService(HttpClient http, NetworkStatusService networkStatus, IServiceProvider serviceProvider)
|
||||
{
|
||||
_http = http;
|
||||
_networkStatus = networkStatus;
|
||||
_jsRuntime = serviceProvider.GetService(typeof(IJSRuntime)) as IJSRuntime;
|
||||
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 5256
|
||||
? "/graphql"
|
||||
: "http://localhost:5256/graphql";
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => !string.IsNullOrEmpty(AuthToken);
|
||||
public string? CurrentUsername { get; private set; }
|
||||
public string? AuthToken { get; private set; }
|
||||
|
||||
public event Action? AuthStateChanged;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
if (_jsRuntime != null)
|
||||
try
|
||||
{
|
||||
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))
|
||||
{
|
||||
AuthToken = storedToken;
|
||||
CurrentUsername = storedUser;
|
||||
AuthStateChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// LocalStorage not available in testing or SSR
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LoginAsync(string username, string password)
|
||||
{
|
||||
if (!_networkStatus.IsOnline)
|
||||
return new AuthResult { Success = false, Message = "Cannot login while offline." };
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"mutation Login($username: String!, $password: String!) {
|
||||
login(username: $username, password: $password) {
|
||||
success
|
||||
token
|
||||
username
|
||||
message
|
||||
}
|
||||
}",
|
||||
Variables = new Dictionary<string, object>
|
||||
{
|
||||
["username"] = username,
|
||||
["password"] = password
|
||||
}
|
||||
};
|
||||
|
||||
var response = await SendGraphQLAsync<LoginResponseData>(request);
|
||||
var payload = response?.Data?.Login;
|
||||
if (payload != null && payload.Success && !string.IsNullOrEmpty(payload.Token))
|
||||
{
|
||||
AuthToken = payload.Token;
|
||||
CurrentUsername = payload.Username ?? username;
|
||||
|
||||
if (_jsRuntime != null)
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "chrono_auth_token", AuthToken);
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "chrono_auth_user", CurrentUsername);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
AuthStateChanged?.Invoke();
|
||||
return new AuthResult { Success = true, Message = payload.Message ?? "Login successful" };
|
||||
}
|
||||
|
||||
return new AuthResult { Success = false, Message = payload?.Message ?? "Invalid username or password." };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new AuthResult { Success = false, Message = $"Login error: {ex.Message}" };
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
AuthToken = null;
|
||||
CurrentUsername = null;
|
||||
|
||||
if (_jsRuntime != null)
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "chrono_auth_token");
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "chrono_auth_user");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
AuthStateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetAuth(string username, string token)
|
||||
{
|
||||
CurrentUsername = username;
|
||||
AuthToken = token;
|
||||
AuthStateChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task<GraphQLResponse<T>?> SendGraphQLAsync<T>(GraphQLRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpResponse = await _http.PostAsJsonAsync(_graphqlEndpoint, request);
|
||||
if (httpResponse.IsSuccessStatusCode)
|
||||
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
|
||||
if (_graphqlEndpoint == "/graphql")
|
||||
{
|
||||
var fallbackResponse = await _http.PostAsJsonAsync("http://localhost:5256/graphql", request);
|
||||
if (fallbackResponse.IsSuccessStatusCode)
|
||||
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var fallbackResponse = await _http.PostAsJsonAsync("http://localhost:5256/graphql", request);
|
||||
if (fallbackResponse.IsSuccessStatusCode)
|
||||
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public class AuthResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
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 LoginResponseData
|
||||
{
|
||||
[JsonPropertyName("login")] public LoginPayloadData? Login { get; set; }
|
||||
}
|
||||
|
||||
private class LoginPayloadData
|
||||
{
|
||||
[JsonPropertyName("success")] public bool Success { get; set; }
|
||||
|
||||
[JsonPropertyName("token")] public string? Token { get; set; }
|
||||
|
||||
[JsonPropertyName("username")] public string? Username { get; set; }
|
||||
|
||||
[JsonPropertyName("message")] public string? Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Model;
|
||||
|
||||
namespace Web.Services;
|
||||
|
||||
public class CardNotesGraphQLService
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
private readonly string _graphqlEndpoint;
|
||||
private readonly HttpClient _http;
|
||||
private readonly NetworkStatusService _networkStatus;
|
||||
|
||||
public CardNotesGraphQLService(HttpClient http, NetworkStatusService networkStatus, AuthService authService)
|
||||
{
|
||||
_http = http;
|
||||
_networkStatus = networkStatus;
|
||||
_authService = authService;
|
||||
_graphqlEndpoint = http.BaseAddress != null && http.BaseAddress.Port == 5256
|
||||
? "/graphql"
|
||||
: "http://localhost:5256/graphql";
|
||||
}
|
||||
|
||||
public async Task<bool> IsApiOnlineAsync()
|
||||
{
|
||||
if (!_networkStatus.IsOnline) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = "query { __typename }"
|
||||
};
|
||||
var response = await SendGraphQLAsync<object>(request);
|
||||
return response != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetNoteAsync(string cardName)
|
||||
{
|
||||
if (!_networkStatus.IsOnline || !_authService.IsLoggedIn) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"query GetCardNote($cardName: String!) {
|
||||
note(cardName: $cardName) {
|
||||
username
|
||||
cardName
|
||||
note
|
||||
}
|
||||
}",
|
||||
Variables = new Dictionary<string, object>
|
||||
{
|
||||
["cardName"] = cardName
|
||||
}
|
||||
};
|
||||
|
||||
var response = await SendGraphQLAsync<GetNoteData>(request);
|
||||
return response?.Data?.Note?.Note ?? string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[WARNING] Failed to fetch note for {cardName}: {ex.Message}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SaveNoteAsync(string cardName, string note)
|
||||
{
|
||||
if (!_networkStatus.IsOnline || !_authService.IsLoggedIn) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"mutation SaveCardNote($cardName: String!, $note: String!) {
|
||||
saveNote(cardName: $cardName, note: $note) {
|
||||
username
|
||||
cardName
|
||||
note
|
||||
}
|
||||
}",
|
||||
Variables = new Dictionary<string, object>
|
||||
{
|
||||
["cardName"] = cardName,
|
||||
["note"] = note
|
||||
}
|
||||
};
|
||||
|
||||
var response = await SendGraphQLAsync<SaveNoteData>(request);
|
||||
return response?.Data?.SaveNote != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[WARNING] Failed to save note for {cardName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GraphQLResponse<T>?> SendGraphQLAsync<T>(GraphQLRequest request)
|
||||
{
|
||||
async Task<HttpResponseMessage> SendToUrlAsync(string url)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
Content = JsonContent.Create(request)
|
||||
};
|
||||
if (!string.IsNullOrEmpty(_authService.AuthToken))
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authService.AuthToken);
|
||||
return await _http.SendAsync(httpRequest);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var httpResponse = await SendToUrlAsync(_graphqlEndpoint);
|
||||
if (httpResponse.IsSuccessStatusCode)
|
||||
return await httpResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
|
||||
if (_graphqlEndpoint == "/graphql")
|
||||
{
|
||||
var fallbackResponse = await SendToUrlAsync("http://localhost:5256/graphql");
|
||||
if (fallbackResponse.IsSuccessStatusCode)
|
||||
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var fallbackResponse = await SendToUrlAsync("http://localhost:5256/graphql");
|
||||
if (fallbackResponse.IsSuccessStatusCode)
|
||||
return await fallbackResponse.Content.ReadFromJsonAsync<GraphQLResponse<T>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public class GraphQLRequest
|
||||
{
|
||||
[JsonPropertyName("query")] public string Query { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("variables")] public Dictionary<string, object>? Variables { get; set; }
|
||||
}
|
||||
|
||||
public class GraphQLResponse<T>
|
||||
{
|
||||
[JsonPropertyName("data")] public T? Data { get; set; }
|
||||
|
||||
[JsonPropertyName("errors")] public List<GraphQLError>? Errors { get; set; }
|
||||
}
|
||||
|
||||
public class GraphQLError
|
||||
{
|
||||
[JsonPropertyName("message")] public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class GetNoteData
|
||||
{
|
||||
[JsonPropertyName("note")] public CardNote? Note { get; set; }
|
||||
}
|
||||
|
||||
public class SaveNoteData
|
||||
{
|
||||
[JsonPropertyName("saveNote")] public CardNote? SaveNote { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,19 @@
|
||||
namespace Web.Services;
|
||||
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Web.Services;
|
||||
|
||||
public class NetworkStatusService : IAsyncDisposable, IDisposable
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private DotNetObjectReference<NetworkStatusService>? _dotNetRef;
|
||||
private bool _isOnline = true;
|
||||
private bool _initialized;
|
||||
|
||||
public event Action<bool>? StatusChanged;
|
||||
|
||||
public bool IsOnline => _isOnline;
|
||||
|
||||
public NetworkStatusService(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
_dotNetRef = DotNetObjectReference.Create(this);
|
||||
_isOnline = await _jsRuntime.InvokeAsync<bool>("networkStatus.initialize", _dotNetRef);
|
||||
StatusChanged?.Invoke(_isOnline);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NetworkStatusService] Failed to initialize: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void SetOnlineStatus(bool isOnline)
|
||||
{
|
||||
if (_isOnline != isOnline)
|
||||
{
|
||||
_isOnline = isOnline;
|
||||
StatusChanged?.Invoke(_isOnline);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dotNetRef?.Dispose();
|
||||
_dotNetRef = null;
|
||||
}
|
||||
public bool IsOnline { get; private set; } = true;
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
@@ -63,7 +27,43 @@ public class NetworkStatusService : IAsyncDisposable, IDisposable
|
||||
{
|
||||
// Ignore disposal errors
|
||||
}
|
||||
|
||||
_dotNetRef.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dotNetRef?.Dispose();
|
||||
_dotNetRef = null;
|
||||
}
|
||||
|
||||
public event Action<bool>? StatusChanged;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
_dotNetRef = DotNetObjectReference.Create(this);
|
||||
IsOnline = await _jsRuntime.InvokeAsync<bool>("networkStatus.initialize", _dotNetRef);
|
||||
StatusChanged?.Invoke(IsOnline);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NetworkStatusService] Failed to initialize: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void SetOnlineStatus(bool isOnline)
|
||||
{
|
||||
if (IsOnline != isOnline)
|
||||
{
|
||||
IsOnline = isOnline;
|
||||
StatusChanged?.Invoke(IsOnline);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user