using ModVersionChecker.repository.api.dto; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Collections.Generic; using System.Threading.Tasks; namespace ModVersionChecker.repository.api { public class ApiRepository : IApiRepository, IDisposable { private readonly HttpClient _httpClient; private string baseUrl = "http://192.168.1.115:3115/api"; private JwtTokenResponse? _accessToken; private JwtTokenResponse? _refreshToken; private DateTime _accessTokenExpiry = DateTime.MinValue; public ApiRepository() { _httpClient = new HttpClient(); _httpClient.Timeout = TimeSpan.FromSeconds(30); } public async Task AuthenticateAsync(string username, string password) { var url = $"{baseUrl}/auth"; var payload = new { username, password }; var jsonPayload = JsonSerializer.Serialize(payload); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); try { var response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var authentication = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); _accessToken = DecodeJwt(authentication?.AccessToken); _refreshToken = DecodeJwt(authentication?.RefreshToken); if (_accessToken == null) throw new Exception("Failed to decode access token."); _accessTokenExpiry = DateTime.UtcNow.AddSeconds(_accessToken.ExpireAt - 60); return true; } catch { return false; } } private JwtTokenResponse? DecodeJwt(string? token) { if (string.IsNullOrEmpty(token)) return null; var parts = token.Split('.'); if (parts.Length != 3) throw new ArgumentException("Invalid JWT token format."); var payload = parts[1]; var paddedPayload = payload.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '='); var jsonBytes = Convert.FromBase64String(paddedPayload); var json = Encoding.UTF8.GetString(jsonBytes); var doc = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (doc != null) doc.Token = token; return doc; } private async Task EnsureTokenValidAsync() { if (_accessToken == null || DateTime.UtcNow >= _accessTokenExpiry) { return await RefreshTokenAsync(); } return true; } private async Task RefreshTokenAsync() { if (_refreshToken == null) return false; var refreshData = new { refreshToken = _refreshToken }; var content = new StringContent(JsonSerializer.Serialize(refreshData), Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync($"{baseUrl}/auth/refresh", content); if (!response.IsSuccessStatusCode) return false; var json = await response.Content.ReadAsStringAsync(); var authentication = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); _accessToken = DecodeJwt(authentication?.AccessToken); if (_accessToken == null) throw new Exception("Failed to decode access token."); _accessTokenExpiry = DateTime.UtcNow.AddSeconds(_accessToken.ExpireAt - 60); return true; } private async Task CreateRequestAsync(HttpMethod method, string url) { await EnsureTokenValidAsync(); var request = new HttpRequestMessage(method, url); if (_accessToken != null) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken.Token); return request; } public async Task?> GetAppVersionsAsync(List apps) { var url = $"{baseUrl}/app/versions?{string.Join("&", apps.Select(a => $"version={Uri.EscapeDataString(a.Id)}"))}"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public async Task GetAppLatestVersionAsync(App app) { var url = $"{baseUrl}/app/latest?version={Uri.EscapeDataString(app.Id)}"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public async Task> GetTypes() { var url = $"{baseUrl}/type"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public async Task> GetSources() { var url = $"{baseUrl}/source"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public async Task?> SearchApps(string searchText) { var url = $"{baseUrl}/app/search?q={Uri.EscapeDataString(searchText)}"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } public async Task> GetAppsByIds(List apps) { var query = string.Join("&", apps.Select(a => $"id={Uri.EscapeDataString(a.Id)}")); var url = $"{baseUrl}/app/search?{query}"; var request = await CreateRequestAsync(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); } public void Dispose() { _httpClient?.Dispose(); } } }