Compare commits
6 Commits
0.1.0-test
...
main
Author | SHA1 | Date | |
---|---|---|---|
b498ec8616 | |||
c6e6b29905 | |||
854658f732 | |||
075a803f4f | |||
1c1b2877cf | |||
35c09f0b3e |
@ -57,10 +57,4 @@ jobs:
|
||||
dotnet nuget push ./artifacts/*.nupkg \
|
||||
--api-key ${{ secrets.NUGET_API_KEY }} \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--skip-duplicate
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nuget-packages-${{ env.VERSION }}
|
||||
path: ./artifacts/*.nupkg
|
||||
--skip-duplicate
|
@ -2,10 +2,10 @@ namespace Svrnty.GeoManagement.Abstractions.Abstractions;
|
||||
|
||||
public interface IAddress
|
||||
{
|
||||
public string Line1 { get; }
|
||||
public string? Line2 { get; }
|
||||
public string City { get; }
|
||||
public string Subdivision { get; }
|
||||
public string PostalCode { get; }
|
||||
public string Country { get; }
|
||||
public string Line1 { get; set; }
|
||||
public string? Line2 { get; set; }
|
||||
public string City { get; set; }
|
||||
public string Subdivision { get; set; }
|
||||
public string PostalCode { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
}
|
@ -2,60 +2,32 @@ using Svrnty.GeoManagement.Abstractions.Abstractions;
|
||||
|
||||
namespace Svrnty.GeoManagement.Abstractions.Models;
|
||||
|
||||
public record GeoPoint(double Latitude, double Longitude);
|
||||
public record GeoPoint(decimal Latitude, decimal Longitude);
|
||||
|
||||
public record Address(
|
||||
string Line1,
|
||||
string? Line2,
|
||||
string City,
|
||||
string Subdivision,
|
||||
string PostalCode,
|
||||
string Country,
|
||||
GeoPoint? Location,
|
||||
string? Note,
|
||||
bool IsNormalized = false
|
||||
) : IAddress
|
||||
public record Address(bool Normalized = false) : IAddress
|
||||
{
|
||||
public string GetFormattedAddress(
|
||||
FormattedAddressType formatType = FormattedAddressType.Full
|
||||
)
|
||||
public required string Line1 { get; set; }
|
||||
public string? Line2 { get; set; }
|
||||
public required string City { get; set; }
|
||||
public required string Subdivision { get; set; }
|
||||
public required string PostalCode { get; set; }
|
||||
public required string CountryCode { get; set; }
|
||||
public GeoPoint? Location { get; set; }
|
||||
public string? Note { get; set; }
|
||||
public bool IsNormalized() => Normalized;
|
||||
|
||||
public static Address CreateFrom(IAddress address, GeoPoint? location = null, string? note = null, bool normalized = false)
|
||||
{
|
||||
return formatType switch
|
||||
return new Address(normalized)
|
||||
{
|
||||
FormattedAddressType.Full => FormatFullOneLine(),
|
||||
FormattedAddressType.Compact => FormatCompactOneLine(),
|
||||
FormattedAddressType.MultiLine => FormatMultiLine(),
|
||||
_ => FormatFullOneLine()
|
||||
Line1 = address.Line1,
|
||||
Line2 = address.Line2,
|
||||
City = address.City,
|
||||
Subdivision = address.Subdivision,
|
||||
PostalCode = address.PostalCode,
|
||||
CountryCode = address.CountryCode,
|
||||
Location = location,
|
||||
Note = note,
|
||||
};
|
||||
}
|
||||
|
||||
private string FormatFullOneLine()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Line2))
|
||||
{
|
||||
return $"{Line1}, {City}, {Subdivision} {PostalCode}, {Country}";
|
||||
}
|
||||
|
||||
return $"{Line2}, {Line1}, {City}, {Subdivision} {PostalCode}, {Country}";
|
||||
}
|
||||
|
||||
private string FormatCompactOneLine()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Line2))
|
||||
{
|
||||
return $"{Line1}, {City}, {Subdivision}";
|
||||
}
|
||||
|
||||
return $"{Line2}, {Line1}, {City}, {Subdivision}";
|
||||
}
|
||||
|
||||
private string FormatMultiLine()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Line2))
|
||||
{
|
||||
return $"{Line1}\n{City}, {Subdivision} {PostalCode}\n{Country}";
|
||||
}
|
||||
|
||||
return $"{Line2}, {Line1}\n{City}, {Subdivision} {PostalCode}\n{Country}";
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Svrnty.GeoManagement.Abstractions.Abstractions;
|
||||
using Svrnty.GeoManagement.Abstractions.Models;
|
||||
|
||||
namespace Svrnty.GeoManagement.Abstractions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static void To(this IAddress address, IAddress toAddress)
|
||||
{
|
||||
toAddress.Line1 = address.Line1;
|
||||
toAddress.Line2 = address.Line2;
|
||||
toAddress.City = address.City;
|
||||
toAddress.PostalCode = address.PostalCode;
|
||||
toAddress.CountryCode = address.CountryCode;
|
||||
toAddress.Subdivision = address.Subdivision;
|
||||
}
|
||||
|
||||
public static void From(this IAddress address, IAddress fromAddress)
|
||||
{
|
||||
address.Line1 = fromAddress.Line1;
|
||||
address.Line2 = fromAddress.Line2;
|
||||
address.City = fromAddress.City;
|
||||
address.PostalCode = fromAddress.PostalCode;
|
||||
address.CountryCode = fromAddress.CountryCode;
|
||||
address.Subdivision = fromAddress.Subdivision;
|
||||
}
|
||||
|
||||
public static string GetFormattedAddress(this IAddress address, FormattedAddressType formatType = FormattedAddressType.Full)
|
||||
{
|
||||
return formatType switch
|
||||
{
|
||||
FormattedAddressType.Full => FormatFullOneLine(address),
|
||||
FormattedAddressType.Compact => FormatCompactOneLine(address),
|
||||
FormattedAddressType.MultiLine => FormatMultiLine(address),
|
||||
_ => FormatFullOneLine(address)
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatFullOneLine(IAddress address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address.Line2))
|
||||
{
|
||||
return $"{address.Line1}, {address.City}, {address.Subdivision} {address.PostalCode}, {address.CountryCode}";
|
||||
}
|
||||
|
||||
return $"{address.Line2}, {address.Line1}, {address.City}, {address.Subdivision} {address.PostalCode}, {address.CountryCode}";
|
||||
}
|
||||
|
||||
private static string FormatCompactOneLine(IAddress address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address.Line2))
|
||||
{
|
||||
return $"{address.Line1}, {address.City}, {address.Subdivision}";
|
||||
}
|
||||
|
||||
return $"{address.Line2}, {address.Line1}, {address.City}, {address.Subdivision}";
|
||||
}
|
||||
|
||||
private static string FormatMultiLine(IAddress address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address.Line2))
|
||||
{
|
||||
return $"{address.Line1}\n{address.City}, {address.Subdivision} {address.PostalCode}\n{address.CountryCode}";
|
||||
}
|
||||
|
||||
return $"{address.Line2}, {address.Line1}\n{address.City}, {address.Subdivision} {address.PostalCode}\n{address.CountryCode}";
|
||||
}
|
||||
|
||||
public static string GetHash(this IAddress address)
|
||||
=> ComputeSha256Hex(address.GetFormattedAddress());
|
||||
|
||||
private static string ComputeSha256Hex(string input)
|
||||
{
|
||||
using SHA256 sha256 = SHA256.Create();
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hash = sha256.ComputeHash(bytes);
|
||||
|
||||
return Convert.ToHexString(hash).ToLower();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using GoogleApi.Entities.Maps.Geocoding.Address.Request;
|
||||
using GoogleApi.Entities.Maps.Geocoding.Location.Request;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Svrnty.GeoManagement.Abstractions;
|
||||
using Svrnty.GeoManagement.Abstractions.Abstractions;
|
||||
using Svrnty.GeoManagement.Abstractions.Models;
|
||||
using Svrnty.GeoManagement.Google.Configuration;
|
||||
@ -91,7 +92,7 @@ public class GeoManagementGoogleProvider : IGeoManagementProvider
|
||||
var request = new LocationGeocodeRequest
|
||||
{
|
||||
Key = _options.ApiKey,
|
||||
Location = new GoogleApi.Entities.Common.Coordinate(geoPoint.Latitude, geoPoint.Longitude)
|
||||
Location = new GoogleApi.Entities.Common.Coordinate((double)geoPoint.Latitude, (double)geoPoint.Longitude)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_options.Language) &&
|
||||
|
@ -26,20 +26,20 @@ internal static class GoogleAddressMapper
|
||||
var country = GetComponentValue(components, "country") ?? string.Empty;
|
||||
|
||||
var location = googleResult.Geometry?.Location != null
|
||||
? new GeoPoint(googleResult.Geometry.Location.Latitude, googleResult.Geometry.Location.Longitude)
|
||||
? new GeoPoint((decimal)googleResult.Geometry.Location.Latitude, (decimal)googleResult.Geometry.Location.Longitude)
|
||||
: null;
|
||||
|
||||
return new Abstractions.Models.Address(
|
||||
Line1: line1,
|
||||
Line2: line2,
|
||||
City: city,
|
||||
Subdivision: subdivision,
|
||||
PostalCode: postalCode,
|
||||
Country: country,
|
||||
Location: location,
|
||||
Note: null,
|
||||
IsNormalized: true
|
||||
);
|
||||
return new Abstractions.Models.Address(Normalized: true)
|
||||
{
|
||||
Line1 = line1,
|
||||
Line2 = line2,
|
||||
City = city,
|
||||
Subdivision = subdivision,
|
||||
PostalCode = postalCode,
|
||||
CountryCode = country,
|
||||
Location = location,
|
||||
Note = null
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildLine1(IEnumerable<AddressComponent> components)
|
||||
@ -79,8 +79,8 @@ internal static class GoogleAddressMapper
|
||||
return null;
|
||||
|
||||
return new GeoPoint(
|
||||
googleResult.Geometry.Location.Latitude,
|
||||
googleResult.Geometry.Location.Longitude
|
||||
(decimal)googleResult.Geometry.Location.Latitude,
|
||||
(decimal)googleResult.Geometry.Location.Longitude
|
||||
);
|
||||
}
|
||||
}
|
@ -24,12 +24,12 @@ public class AddressTestData
|
||||
|
||||
public class GeoPointTestData
|
||||
{
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public decimal Latitude { get; set; }
|
||||
public decimal Longitude { get; set; }
|
||||
}
|
||||
|
||||
public class CoordinateRange
|
||||
{
|
||||
public double Min { get; set; }
|
||||
public double Max { get; set; }
|
||||
public decimal Min { get; set; }
|
||||
public decimal Max { get; set; }
|
||||
}
|
||||
|
@ -54,15 +54,17 @@ public class GoogleProviderTests : IDisposable
|
||||
public async Task GetGeoPointAsync_WithValidAddress_ReturnsCoordinates()
|
||||
{
|
||||
// Arrange
|
||||
var address = new Address(
|
||||
Line1: _testData.ValidAddress.Line1,
|
||||
Line2: _testData.ValidAddress.Line2,
|
||||
City: _testData.ValidAddress.City,
|
||||
Subdivision: _testData.ValidAddress.Subdivision,
|
||||
PostalCode: _testData.ValidAddress.PostalCode,
|
||||
Country: _testData.ValidAddress.Country,
|
||||
Location: null,
|
||||
Note: null);
|
||||
var address = new Address
|
||||
{
|
||||
Line1 = _testData.ValidAddress.Line1,
|
||||
Line2 = _testData.ValidAddress.Line2,
|
||||
City = _testData.ValidAddress.City,
|
||||
Subdivision = _testData.ValidAddress.Subdivision,
|
||||
PostalCode = _testData.ValidAddress.PostalCode,
|
||||
CountryCode = _testData.ValidAddress.Country,
|
||||
Location = null,
|
||||
Note = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _provider.GetGeoPointAsync(address);
|
||||
@ -88,8 +90,8 @@ public class GoogleProviderTests : IDisposable
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Line1);
|
||||
Assert.NotNull(result.City);
|
||||
Assert.NotNull(result.Country);
|
||||
Assert.True(result.IsNormalized);
|
||||
Assert.NotNull(result.CountryCode);
|
||||
Assert.True(result.IsNormalized());
|
||||
Assert.NotNull(result.Location);
|
||||
}
|
||||
|
||||
@ -97,22 +99,24 @@ public class GoogleProviderTests : IDisposable
|
||||
public async Task NormalizeAddressAsync_WithAddressObject_ReturnsNormalizedAddress()
|
||||
{
|
||||
// Arrange
|
||||
var address = new Address(
|
||||
Line1: _testData.NormalizeTestAddress.Line1,
|
||||
Line2: _testData.NormalizeTestAddress.Line2,
|
||||
City: _testData.NormalizeTestAddress.City,
|
||||
Subdivision: _testData.NormalizeTestAddress.Subdivision,
|
||||
PostalCode: _testData.NormalizeTestAddress.PostalCode,
|
||||
Country: _testData.NormalizeTestAddress.Country,
|
||||
Location: null,
|
||||
Note: null);
|
||||
var address = new Address
|
||||
{
|
||||
Line1 = _testData.NormalizeTestAddress.Line1,
|
||||
Line2 = _testData.NormalizeTestAddress.Line2,
|
||||
City = _testData.NormalizeTestAddress.City,
|
||||
Subdivision = _testData.NormalizeTestAddress.Subdivision,
|
||||
PostalCode = _testData.NormalizeTestAddress.PostalCode,
|
||||
CountryCode = _testData.NormalizeTestAddress.Country,
|
||||
Location = null,
|
||||
Note = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _provider.NormalizeAddressAsync(address);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsNormalized);
|
||||
Assert.True(result.IsNormalized());
|
||||
Assert.NotNull(result.Location);
|
||||
Assert.NotNull(result.Line1);
|
||||
Assert.Contains("Amphitheatre", result.Line1, StringComparison.OrdinalIgnoreCase);
|
||||
@ -129,7 +133,7 @@ public class GoogleProviderTests : IDisposable
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsNormalized);
|
||||
Assert.True(result.IsNormalized());
|
||||
Assert.NotNull(result.Location);
|
||||
Assert.NotNull(result.Line1);
|
||||
Assert.Contains("Mountain View", result.City, StringComparison.OrdinalIgnoreCase);
|
||||
@ -139,15 +143,17 @@ public class GoogleProviderTests : IDisposable
|
||||
public async Task GetGeoPointAsync_WithInvalidAddress_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var address = new Address(
|
||||
Line1: _testData.InvalidAddress.Line1,
|
||||
Line2: _testData.InvalidAddress.Line2,
|
||||
City: _testData.InvalidAddress.City,
|
||||
Subdivision: _testData.InvalidAddress.Subdivision,
|
||||
PostalCode: _testData.InvalidAddress.PostalCode,
|
||||
Country: _testData.InvalidAddress.Country,
|
||||
Location: null,
|
||||
Note: null);
|
||||
var address = new Address
|
||||
{
|
||||
Line1 = _testData.InvalidAddress.Line1,
|
||||
Line2 = _testData.InvalidAddress.Line2,
|
||||
City = _testData.InvalidAddress.City,
|
||||
Subdivision = _testData.InvalidAddress.Subdivision,
|
||||
PostalCode = _testData.InvalidAddress.PostalCode,
|
||||
CountryCode = _testData.InvalidAddress.Country,
|
||||
Location = null,
|
||||
Note = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _provider.GetGeoPointAsync(address);
|
||||
@ -172,7 +178,7 @@ public class GoogleProviderTests : IDisposable
|
||||
// This test just verifies it doesn't crash
|
||||
if (result != null)
|
||||
{
|
||||
Assert.True(result.IsNormalized);
|
||||
Assert.True(result.IsNormalized());
|
||||
}
|
||||
}
|
||||
|
||||
|
2
Svrnty.GeoManagement.sln.DotSettings.user
Normal file
2
Svrnty.GeoManagement.sln.DotSettings.user
Normal file
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F728eee8ccad23b8fff516dba093eb71b49fb5ee8839f2118392e9747de4ebb_003FResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
Loading…
Reference in New Issue
Block a user