mirror of
https://dev.azure.com/tstanciu94/PhantomMind/_git/Bitip
synced 2025-10-13 01:52:19 +03:00
Compare commits
2 Commits
50320f8591
...
f227523fce
Author | SHA1 | Date | |
---|---|---|---|
|
f227523fce | ||
|
27b3073907 |
@ -75,7 +75,7 @@ class GeoIPService {
|
|||||||
const result: SimplifiedGeoIPResponse = {
|
const result: SimplifiedGeoIPResponse = {
|
||||||
ip,
|
ip,
|
||||||
country: cityResponse.country?.names?.en || 'Unknown',
|
country: cityResponse.country?.names?.en || 'Unknown',
|
||||||
country_code: cityResponse.country?.isoCode || 'XX',
|
country_code: cityResponse.country?.isoCode || 'Unknown',
|
||||||
is_in_european_union: cityResponse.country?.isInEuropeanUnion || false,
|
is_in_european_union: cityResponse.country?.isInEuropeanUnion || false,
|
||||||
region: cityResponse.subdivisions?.[0]?.names?.en || 'Unknown',
|
region: cityResponse.subdivisions?.[0]?.names?.en || 'Unknown',
|
||||||
region_code: cityResponse.subdivisions?.[0]?.isoCode || null,
|
region_code: cityResponse.subdivisions?.[0]?.isoCode || null,
|
||||||
@ -111,9 +111,12 @@ class GeoIPService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response: City = this.cityReader!.city(ip);
|
const response: City = this.cityReader!.city(ip);
|
||||||
|
const asnResponse: Asn = this.asnReader!.asn(ip);
|
||||||
|
|
||||||
const result: DetailedGeoIPResponse = {
|
const result: DetailedGeoIPResponse = {
|
||||||
ip,
|
ip,
|
||||||
location: response as GeoIPLocation,
|
location: response as GeoIPLocation,
|
||||||
|
asn: asnResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cache.set(cacheKey, result);
|
this.cache.set(cacheKey, result);
|
||||||
|
@ -37,6 +37,13 @@ export interface GeoIPLocation {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AsnInfo {
|
||||||
|
autonomousSystemNumber?: number;
|
||||||
|
autonomousSystemOrganization?: string;
|
||||||
|
ipAddress?: string;
|
||||||
|
network?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimplifiedGeoIPResponse {
|
export interface SimplifiedGeoIPResponse {
|
||||||
ip: string;
|
ip: string;
|
||||||
country: string;
|
country: string;
|
||||||
@ -58,6 +65,7 @@ export interface SimplifiedGeoIPResponse {
|
|||||||
export interface DetailedGeoIPResponse {
|
export interface DetailedGeoIPResponse {
|
||||||
ip: string;
|
ip: string;
|
||||||
location: GeoIPLocation;
|
location: GeoIPLocation;
|
||||||
|
asn: AsnInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BatchGeoIPRequest {
|
export interface BatchGeoIPRequest {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Bitip.Client.Tests
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
namespace Bitip.Client.Tests
|
||||||
{
|
{
|
||||||
public class Class1
|
public class Class1
|
||||||
{
|
{
|
||||||
|
@ -5,4 +5,9 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -11,6 +11,14 @@ namespace Bitip.Client.Constants
|
|||||||
{
|
{
|
||||||
public const string
|
public const string
|
||||||
Health = "health",
|
Health = "health",
|
||||||
Version = "version";
|
Version = "version",
|
||||||
|
Lookup = "lookup?ip={ip}",
|
||||||
|
DetailedLookup = "lookup/detailed?ip={ip}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ValueKeys
|
||||||
|
{
|
||||||
|
public const string
|
||||||
|
MissingValue = "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Extensions
|
||||||
|
{
|
||||||
|
internal static class HttpClientExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Performs GET request, ensures success, and deserializes response
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<T> GetAndReadJsonAsync<T>(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var response = await httpClient.GetAsync(requestUri, cancellationToken);
|
||||||
|
var data = await response.EnsureSuccessAndReadJsonAsync<T>(cancellationToken);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
using Bitip.Client.Models;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bitip.Client.Extensions
|
|
||||||
{
|
|
||||||
internal static class HttpMessageExtensions
|
|
||||||
{
|
|
||||||
public static async Task<HttpResponseMessage> EnsureSuccessOperation(this HttpResponseMessage response, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
return response;
|
|
||||||
var errorResponse = await response.Content.ReadFromJsonAsync<ErrorResponse>(cancellationToken);
|
|
||||||
if (errorResponse is null)
|
|
||||||
throw new HttpRequestException("An error occurred while fetching Bitip information.");
|
|
||||||
var message = $"{errorResponse.Error}: {errorResponse.Message}";
|
|
||||||
if (!string.IsNullOrWhiteSpace(errorResponse.Ip))
|
|
||||||
message += $" (IP: {errorResponse.Ip})";
|
|
||||||
throw new HttpRequestException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
using Bitip.Client.Models;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Extensions
|
||||||
|
{
|
||||||
|
internal static class HttpResponseMessageExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the HTTP response indicates success; otherwise, throws an exception with error details.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<HttpResponseMessage> EnsureSuccessOperation(this HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return response;
|
||||||
|
var errorResponse = await response.Content.ReadFromJsonAsync<ErrorResponse>(cancellationToken);
|
||||||
|
if (errorResponse is null)
|
||||||
|
throw new HttpRequestException("An error occurred while fetching Bitip information.");
|
||||||
|
var message = $"{errorResponse.Error}: {errorResponse.Message}";
|
||||||
|
if (!string.IsNullOrWhiteSpace(errorResponse.Ip))
|
||||||
|
message += $" (IP: {errorResponse.Ip})";
|
||||||
|
throw new HttpRequestException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads JSON content and ensures it's not null
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<T> ReadFromJsonOrThrowAsync<T>(this HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var data = await response.Content.ReadFromJsonAsync<T>(cancellationToken);
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Failed to deserialize response content to type {typeof(T).Name}. Content was null.");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures success status code and reads JSON content
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<T> EnsureSuccessAndReadJsonAsync<T>(this HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await response.EnsureSuccessOperation(cancellationToken);
|
||||||
|
return await response.ReadFromJsonOrThrowAsync<T>(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/clients/dotnet/Bitip.Client/Helpers/IpValidator.cs
Normal file
19
src/clients/dotnet/Bitip.Client/Helpers/IpValidator.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Helpers
|
||||||
|
{
|
||||||
|
internal static class IpValidator
|
||||||
|
{
|
||||||
|
public static void Validate(string ip)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ip))
|
||||||
|
throw new ArgumentException("Value cannot be null or whitespace.", nameof(ip));
|
||||||
|
// Basic validation for IPv4 and IPv6 formats
|
||||||
|
if (!System.Net.IPAddress.TryParse(ip, out _))
|
||||||
|
throw new ArgumentException("The provided IP address is not valid.", nameof(ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
17
src/clients/dotnet/Bitip.Client/Helpers/UriNormalizer.cs
Normal file
17
src/clients/dotnet/Bitip.Client/Helpers/UriNormalizer.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Helpers
|
||||||
|
{
|
||||||
|
internal static class UriNormalizer
|
||||||
|
{
|
||||||
|
public static Uri EnsureTrailingSlash(string url)
|
||||||
|
{
|
||||||
|
if (url is null) throw new ArgumentNullException(nameof(url));
|
||||||
|
var trimmed = url.Trim();
|
||||||
|
var address = trimmed.EndsWith("/") ? trimmed : $"{trimmed}/";
|
||||||
|
return new Uri(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
src/clients/dotnet/Bitip.Client/Models/DetailedIpLocation.cs
Normal file
124
src/clients/dotnet/Bitip.Client/Models/DetailedIpLocation.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Models
|
||||||
|
{
|
||||||
|
public record DetailedIpLocation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("ip")]
|
||||||
|
public required string Ip { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("location")]
|
||||||
|
public required GeoIpLocation Location { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("asn")]
|
||||||
|
public required AsnInfo Asn { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record GeoIpLocation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("country")]
|
||||||
|
public CountryInfo? Country { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("city")]
|
||||||
|
public CityInfo? City { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("subdivisions")]
|
||||||
|
public List<Subdivision>? Subdivisions { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("location")]
|
||||||
|
public LocationInfo? Location { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("postal")]
|
||||||
|
public PostalInfo? Postal { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("continent")]
|
||||||
|
public ContinentInfo? Continent { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("registered_country")]
|
||||||
|
public CountryInfo? RegisteredCountry { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("traits")]
|
||||||
|
public TraitsInfo? Traits { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CountryInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("iso_code")]
|
||||||
|
public string? IsoCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("names")]
|
||||||
|
public Dictionary<string, string>? Names { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_in_european_union")]
|
||||||
|
public bool? IsInEuropeanUnion { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CityInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("names")]
|
||||||
|
public Dictionary<string, string>? Names { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Subdivision
|
||||||
|
{
|
||||||
|
[JsonPropertyName("iso_code")]
|
||||||
|
public string? IsoCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("names")]
|
||||||
|
public Dictionary<string, string>? Names { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record LocationInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("latitude")]
|
||||||
|
public double? Latitude { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("longitude")]
|
||||||
|
public double? Longitude { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("time_zone")]
|
||||||
|
public string? TimeZone { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PostalInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("code")]
|
||||||
|
public string? Code { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ContinentInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("code")]
|
||||||
|
public string? Code { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("names")]
|
||||||
|
public Dictionary<string, string>? Names { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record TraitsInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("is_anonymous_proxy")]
|
||||||
|
public bool? IsAnonymousProxy { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_satellite_provider")]
|
||||||
|
public bool? IsSatelliteProvider { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record AsnInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("autonomousSystemNumber")]
|
||||||
|
public long? AutonomousSystemNumber { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("autonomousSystemOrganization")]
|
||||||
|
public string? AutonomousSystemOrganization { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ipAddress")]
|
||||||
|
public string? IpAddress { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("network")]
|
||||||
|
public string? Network { get; init; }
|
||||||
|
}
|
||||||
|
}
|
54
src/clients/dotnet/Bitip.Client/Models/IpLocation.cs
Normal file
54
src/clients/dotnet/Bitip.Client/Models/IpLocation.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Bitip.Client.Models
|
||||||
|
{
|
||||||
|
public record IpLocation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("ip")]
|
||||||
|
public required string Ip { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("country")]
|
||||||
|
public required string Country { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("country_code")]
|
||||||
|
public required string CountryCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_in_european_union")]
|
||||||
|
public required bool IsInEuropeanUnion { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("region")]
|
||||||
|
public required string Region { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("region_code")]
|
||||||
|
public string? RegionCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("city")]
|
||||||
|
public required string City { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("latitude")]
|
||||||
|
public double? Latitude { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("longitude")]
|
||||||
|
public double? Longitude { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("timezone")]
|
||||||
|
public string? Timezone { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("postal_code")]
|
||||||
|
public string? PostalCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("continent_code")]
|
||||||
|
public string? ContinentCode { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("continent_name")]
|
||||||
|
public string? ContinentName { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("organization")]
|
||||||
|
public string? Organization { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("asn")]
|
||||||
|
public long? Asn { get; init; }
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using System;
|
|||||||
|
|
||||||
namespace Bitip.Client.Models
|
namespace Bitip.Client.Models
|
||||||
{
|
{
|
||||||
internal record HealthResponse
|
public record HealthInfo
|
||||||
{
|
{
|
||||||
public required string Status { get; init; }
|
public required string Status { get; init; }
|
||||||
public required string Service { get; init; }
|
public required string Service { get; init; }
|
||||||
@ -12,14 +12,14 @@ namespace Bitip.Client.Models
|
|||||||
public string? Error { get; init; }
|
public string? Error { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal record VersionResponse
|
public record VersionInfo
|
||||||
{
|
{
|
||||||
public required string Version { get; init; }
|
public required string Version { get; init; }
|
||||||
public required string CommitHash { get; init; }
|
public required string CommitHash { get; init; }
|
||||||
public DateTime BuildDate { get; init; }
|
public DateTime BuildDate { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal record ErrorResponse
|
public record ErrorResponse
|
||||||
{
|
{
|
||||||
public required string Error { get; init; }
|
public required string Error { get; init; }
|
||||||
public required string Message { get; init; }
|
public required string Message { get; init; }
|
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
using Bitip.Client.Constants;
|
using Bitip.Client.Constants;
|
||||||
using Bitip.Client.Extensions;
|
using Bitip.Client.Extensions;
|
||||||
|
using Bitip.Client.Helpers;
|
||||||
using Bitip.Client.Models;
|
using Bitip.Client.Models;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -19,37 +18,39 @@ namespace Bitip.Client.Services
|
|||||||
|
|
||||||
public BitipClient(HttpClient httpClient, IOptions<BitipOptions> options)
|
public BitipClient(HttpClient httpClient, IOptions<BitipOptions> options)
|
||||||
{
|
{
|
||||||
httpClient.BaseAddress = EnsureTrailingSlash(options.Value.BaseUrl);
|
httpClient.BaseAddress = UriNormalizer.EnsureTrailingSlash(options.Value.BaseUrl);
|
||||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
httpClient.DefaultRequestHeaders.Add(ApiKeys.HttpHeader, options.Value.ApiKey);
|
httpClient.DefaultRequestHeaders.Add(ApiKeys.HttpHeader, options.Value.ApiKey);
|
||||||
|
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HealthResponse> GetHealth(CancellationToken cancellationToken = default)
|
public Task<HealthInfo> GetHealth(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(ApiRoutes.Health, cancellationToken);
|
var task = _httpClient.GetAndReadJsonAsync<HealthInfo>(ApiRoutes.Health, cancellationToken);
|
||||||
await response.EnsureSuccessOperation(cancellationToken);
|
return task;
|
||||||
var data = await response.Content.ReadFromJsonAsync<HealthResponse>(cancellationToken);
|
|
||||||
if (data is null)
|
|
||||||
throw new InvalidOperationException("The response content is null.");
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<VersionResponse> GetVersion(CancellationToken cancellationToken = default)
|
public Task<VersionInfo> GetVersion(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(ApiRoutes.Version, cancellationToken);
|
var task = _httpClient.GetAndReadJsonAsync<VersionInfo>(ApiRoutes.Version, cancellationToken);
|
||||||
await response.EnsureSuccessOperation(cancellationToken);
|
return task;
|
||||||
var data = await response.Content.ReadFromJsonAsync<VersionResponse>(cancellationToken);
|
|
||||||
if (data is null)
|
|
||||||
throw new InvalidOperationException("The response content is null.");
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Uri EnsureTrailingSlash(string url)
|
public Task<IpLocation> GetIpLocation(string ip, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var address = url.EndsWith("/") ? url : $"{url}/";
|
IpValidator.Validate(ip);
|
||||||
return new Uri(address);
|
var route = ApiRoutes.Lookup.Replace("{ip}", ip);
|
||||||
|
var task = _httpClient.GetAndReadJsonAsync<IpLocation>(route, cancellationToken);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DetailedIpLocation> GetDetailedIpLocation(string ip, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
IpValidator.Validate(ip);
|
||||||
|
var route = ApiRoutes.DetailedLookup.Replace("{ip}", ip);
|
||||||
|
var task = await _httpClient.GetAndReadJsonAsync<DetailedIpLocation>(route, cancellationToken);
|
||||||
|
return task;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
// Copyright (c) 2025 Tudor Stanciu
|
// Copyright (c) 2025 Tudor Stanciu
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bitip.Client.Services
|
namespace Bitip.Client.Services
|
||||||
{
|
{
|
||||||
public interface IBitipClient
|
public interface IBitipClient
|
||||||
|
Loading…
x
Reference in New Issue
Block a user