From 3dcab0f64cb8cf423be2ef81c0ee57862427c607 Mon Sep 17 00:00:00 2001 From: Denis Prokhorchik Date: Sun, 16 Jan 2022 17:09:59 +0300 Subject: [PATCH 1/2] feat(issue-113): add empty solution of On-Tracker --- src/Services/on-tracker/O2.OnTracker.sln | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/Services/on-tracker/O2.OnTracker.sln diff --git a/src/Services/on-tracker/O2.OnTracker.sln b/src/Services/on-tracker/O2.OnTracker.sln new file mode 100644 index 00000000..80341bfe --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1700.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D93882A4-4B7E-4814-8462-14D882FB336B} + EndGlobalSection +EndGlobal From dec1c7f1b619e7bf83b96070eaf6e6ad8f3a2d28 Mon Sep 17 00:00:00 2001 From: Denis Prokhorchik Date: Sun, 16 Jan 2022 20:23:38 +0300 Subject: [PATCH 2/2] feat(issue-156): add test skeleton for on-tracker --- .../Controllers/ValuesController.cs | 73 +++++++++++ .../IoC/ServiceCollectionExtensions.cs | 61 +++++++++ .../MaxMindLocalGeoIpAddressResolver.cs | 122 ++++++++++++++++++ .../O2.OnTracker.Api/O2.OnTracker.Api.csproj | 39 ++++++ .../on-tracker/O2.OnTracker.Api/Program.cs | 25 ++++ .../Properties/launchSettings.json | 13 ++ .../O2.OnTracker.Api/Setup/GeoDatabase.cs | 7 + .../on-tracker/O2.OnTracker.Api/Startup.cs | 56 ++++++++ .../appsettings.Development.json | 10 ++ .../O2.OnTracker.Api/appsettings.json | 12 ++ src/Services/on-tracker/O2.OnTracker.sln | 19 +++ .../O2.Tracker.DbUtility/GeoLocation.cs | 34 +++++ .../IGeoIpAddressResolver.cs | 9 ++ .../O2.Tracker.DbUtility.csproj | 17 +++ .../on-tracker/O2.Tracker.DbUtility/Point.cs | 14 ++ 15 files changed, 511 insertions(+) create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/Controllers/ValuesController.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/IoC/ServiceCollectionExtensions.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/MaxMindLocalGeoIpAddressResolver.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/O2.OnTracker.Api.csproj create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/Program.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/Properties/launchSettings.json create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/Setup/GeoDatabase.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/Startup.cs create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/appsettings.Development.json create mode 100644 src/Services/on-tracker/O2.OnTracker.Api/appsettings.json create mode 100644 src/Services/on-tracker/O2.Tracker.DbUtility/GeoLocation.cs create mode 100644 src/Services/on-tracker/O2.Tracker.DbUtility/IGeoIpAddressResolver.cs create mode 100644 src/Services/on-tracker/O2.Tracker.DbUtility/O2.Tracker.DbUtility.csproj create mode 100644 src/Services/on-tracker/O2.Tracker.DbUtility/Point.cs diff --git a/src/Services/on-tracker/O2.OnTracker.Api/Controllers/ValuesController.cs b/src/Services/on-tracker/O2.OnTracker.Api/Controllers/ValuesController.cs new file mode 100644 index 00000000..eac77e4e --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/Controllers/ValuesController.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; +using O2.Tracker.DbUtility; + +namespace O2.OnTracker.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + private readonly IGeoIpAddressResolver _geoIpAddressResolver; + + public ValuesController(IGeoIpAddressResolver geoIpAddressResolver) + { + _geoIpAddressResolver = geoIpAddressResolver; + } + // GET api/values + [HttpGet] + public ActionResult Get() + { + // var ip = HttpContext.Features.Get()?.RemoteIpAddress; + IPAddress remoteIpAddress = HttpContext.Features.Get()?.RemoteIpAddress;//Request.HttpContext.Connection.RemoteIpAddress; + string result = ""; + if (remoteIpAddress != null) + { + // If we got an IPV6 address, then we need to ask the network for the IPV4 address + // This usually only happens when the browser is on the same machine as the server. + if (remoteIpAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) + { + remoteIpAddress = System.Net.Dns.GetHostEntry(remoteIpAddress).AddressList + .First(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork); + } + result = remoteIpAddress.ToString(); + } + + if (result.ToString() == "127.0.0.1") + return Ok("request with localhost"); + return Ok(_geoIpAddressResolver.ResolveAddress(IPAddress.Parse(result.ToString()))); + // return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/IoC/ServiceCollectionExtensions.cs b/src/Services/on-tracker/O2.OnTracker.Api/IoC/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..af777b29 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/IoC/ServiceCollectionExtensions.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace O2.OnTracker.Api.IoC +{ + public static class ServiceCollectionExtensions + { + // ReSharper disable once InconsistentNaming + public static TConfig ConfigurePOCO(this IServiceCollection services, IConfiguration configuration) + where TConfig : class, new() + { + if (services == null) + throw new ArgumentNullException(nameof(services)); + + if (configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + var config = new TConfig(); + configuration.Bind(config); + services.AddSingleton(config); + return config; + } + public static IServiceCollection AddBusiness(this IServiceCollection services) + { + // services.AddSingleton(); + // // Include DataLayer + // // services.AddScoped(); + // //more business services... + // + // services.AddSingleton(); + return services; + } + + public static IServiceCollection AddRequiredMvcComponents(this IServiceCollection services) + { + // services.AddTransient(); + + var mvcBuilder = services.AddMvc(options => + { + // options.Filters.Add(); + }); + // mvcBuilder.SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + //var mvcBuilder = services.AddMvcCore(options => + //{ + // options.Filters.Add(); + //}); + //mvcBuilder.AddJsonFormatters(); + + //mvcBuilder.AddAuthorization(); + // mvcBuilder.AddFormatterMappings(); + //mvcBuilder.AddRazorViewEngine(); + //mvcBuilder.AddRazorPages(); + //mvcBuilder.AddCacheTagHelper(); + //mvcBuilder.AddDataAnnotations(); + + //mvcBuilder.AddCors(); + return services; + } + } +} \ No newline at end of file diff --git a/src/Services/on-tracker/O2.OnTracker.Api/MaxMindLocalGeoIpAddressResolver.cs b/src/Services/on-tracker/O2.OnTracker.Api/MaxMindLocalGeoIpAddressResolver.cs new file mode 100644 index 00000000..ddadf12a --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/MaxMindLocalGeoIpAddressResolver.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using MaxMind.Db; +using O2.OnTracker.Api.Setup; + +namespace O2.Tracker.DbUtility +{ + public sealed class MaxMindLocalGeoIpAddressResolver : IGeoIpAddressResolver + { + private const string DefaultLang = "en"; + + private const FileAccessMode AccessMode = FileAccessMode.Memory; + + // private static readonly ILog m_log = LogManager.GetLogger(typeof(MaxMindLocalGeoIpAddressResolver)); + + private readonly Reader m_reader; + + public MaxMindLocalGeoIpAddressResolver(GeoDatabase setting) + { + // var path = geoDbSetting; + var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + setting.ConnectionDb;//+ "/geoip/" + "GeoLite2-City.mmdb"; + if (string.IsNullOrWhiteSpace(path)) + throw new Exception("MaxMind local database path is not configured"); + + m_reader = new Reader(path); + // if (m_log.IsDebugEnabled) + // m_log.Debug($"{nameof(settings.MaxMindGeoIpDatabasePath)}='{path}'."); + + m_reader = new Reader(path, AccessMode); + } + + public GeoLocation ResolveAddress(IPAddress ip) + { + var response = m_reader.Find(ip); + if (response == null) + return null; + + var result = new GeoLocation + { + Country = response.Country?.Names[DefaultLang], + City = response.City?.Names[DefaultLang] + }; + var location = response.Location; + if (location?.HasCoordinates == true) + result.Point = new Point + { + lat = location.Latitude.Value, + lon = location.Longitude.Value + }; + + return result; + } + + private class NamedEntity + { + [Constructor] + protected NamedEntity( + IDictionary names = null) + { + Names = names != null ? new Dictionary(names) : new Dictionary(); + } + + public IReadOnlyDictionary Names { get; } + } + + private sealed class Country : NamedEntity + { + [Constructor] + public Country( + IDictionary names = null) + : base(names) + { + } + } + + private sealed class Location + { + [Constructor] + public Location( + double? latitude = null, + double? longitude = null) + { + Latitude = latitude; + Longitude = longitude; + } + + public bool HasCoordinates => Latitude.HasValue && Longitude.HasValue; + + public double? Latitude { get; } + + public double? Longitude { get; } + } + + private sealed class GeoLocationData + { + [Constructor] + public GeoLocationData( + Country country, + NamedEntity city, + Location location) + { + Country = country; + City = city; + Location = location; + } + + public Country Country { get; } + public NamedEntity City { get; } + public Location Location { get; } + } + + public void Dispose() + { + m_reader.Dispose(); + } + } +} + + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/O2.OnTracker.Api.csproj b/src/Services/on-tracker/O2.OnTracker.Api/O2.OnTracker.Api.csproj new file mode 100644 index 00000000..86236689 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/O2.OnTracker.Api.csproj @@ -0,0 +1,39 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + + + + + + + + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/Program.cs b/src/Services/on-tracker/O2.OnTracker.Api/Program.cs new file mode 100644 index 00000000..a92deae8 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace O2.OnTracker.Api +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/Properties/launchSettings.json b/src/Services/on-tracker/O2.OnTracker.Api/Properties/launchSettings.json new file mode 100644 index 00000000..8ef69a20 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "O2.OnTracker.Api": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:57549;http://localhost:43192", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/Services/on-tracker/O2.OnTracker.Api/Setup/GeoDatabase.cs b/src/Services/on-tracker/O2.OnTracker.Api/Setup/GeoDatabase.cs new file mode 100644 index 00000000..d25135c0 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/Setup/GeoDatabase.cs @@ -0,0 +1,7 @@ +namespace O2.OnTracker.Api.Setup +{ + public class GeoDatabase + { + public string ConnectionDb { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/on-tracker/O2.OnTracker.Api/Startup.cs b/src/Services/on-tracker/O2.OnTracker.Api/Startup.cs new file mode 100644 index 00000000..06e25a61 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/Startup.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using O2.OnTracker.Api.IoC; +using O2.OnTracker.Api.Setup; +using O2.Tracker.DbUtility; + +namespace O2.OnTracker.Api +{ + public class Startup + { + public Startup(IConfiguration appConfiguration) + { + AppConfiguration = appConfiguration; + } + + public IConfiguration AppConfiguration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + // services.AddSingleton(); + services.ConfigurePOCO(AppConfiguration.GetSection("GeoDatabase")); + services.AddScoped(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/appsettings.Development.json b/src/Services/on-tracker/O2.OnTracker.Api/appsettings.Development.json new file mode 100644 index 00000000..11a51145 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} + diff --git a/src/Services/on-tracker/O2.OnTracker.Api/appsettings.json b/src/Services/on-tracker/O2.OnTracker.Api/appsettings.json new file mode 100644 index 00000000..bdf6eb02 --- /dev/null +++ b/src/Services/on-tracker/O2.OnTracker.Api/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "GeoDatabase": { + "ConnectionDb": "/geoip/GeoLite2-City.mmdb" + } +} + diff --git a/src/Services/on-tracker/O2.OnTracker.sln b/src/Services/on-tracker/O2.OnTracker.sln index 80341bfe..400ecd9b 100644 --- a/src/Services/on-tracker/O2.OnTracker.sln +++ b/src/Services/on-tracker/O2.OnTracker.sln @@ -3,10 +3,26 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 25.0.1700.0 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{897A7BA2-3AAB-4D74-BA13-E6AEF977AE98}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "O2.Tracker.DbUtility", "O2.Tracker.DbUtility\O2.Tracker.DbUtility.csproj", "{77D9E353-F5AE-440B-8EE8-70EBB92099AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "O2.OnTracker.Api", "O2.OnTracker.Api\O2.OnTracker.Api.csproj", "{2288664B-04A5-4C80-8685-2AF877F5D335}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77D9E353-F5AE-440B-8EE8-70EBB92099AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D9E353-F5AE-440B-8EE8-70EBB92099AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D9E353-F5AE-440B-8EE8-70EBB92099AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D9E353-F5AE-440B-8EE8-70EBB92099AD}.Release|Any CPU.Build.0 = Release|Any CPU + {2288664B-04A5-4C80-8685-2AF877F5D335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2288664B-04A5-4C80-8685-2AF877F5D335}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2288664B-04A5-4C80-8685-2AF877F5D335}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2288664B-04A5-4C80-8685-2AF877F5D335}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -14,4 +30,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D93882A4-4B7E-4814-8462-14D882FB336B} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {77D9E353-F5AE-440B-8EE8-70EBB92099AD} = {897A7BA2-3AAB-4D74-BA13-E6AEF977AE98} + EndGlobalSection EndGlobal diff --git a/src/Services/on-tracker/O2.Tracker.DbUtility/GeoLocation.cs b/src/Services/on-tracker/O2.Tracker.DbUtility/GeoLocation.cs new file mode 100644 index 00000000..4d5e6c72 --- /dev/null +++ b/src/Services/on-tracker/O2.Tracker.DbUtility/GeoLocation.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; + +namespace O2.Tracker.DbUtility +{ + [DataContract] + public sealed class GeoLocation + { + [DataMember] + public string Country { get; set; } + + [DataMember] + public string City { get; set; } + + [DataMember] + public Point Point { get; set; } + + public override string ToString() + { + var lat = Format(nameof(Point.lat), Point?.lat); + var lon = Format(nameof(Point.lon), Point?.lon); + + return $"Country={Country}, City={City}{lat}{lon}"; + } + + private static string Format(string name, double? value) + { + if (!value.HasValue) + return null; + + var result = ", " + name + "=" + value.Value; + return result; + } + } +} \ No newline at end of file diff --git a/src/Services/on-tracker/O2.Tracker.DbUtility/IGeoIpAddressResolver.cs b/src/Services/on-tracker/O2.Tracker.DbUtility/IGeoIpAddressResolver.cs new file mode 100644 index 00000000..dd95c22f --- /dev/null +++ b/src/Services/on-tracker/O2.Tracker.DbUtility/IGeoIpAddressResolver.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace O2.Tracker.DbUtility +{ + public interface IGeoIpAddressResolver + { + GeoLocation ResolveAddress(IPAddress ip); + } +} \ No newline at end of file diff --git a/src/Services/on-tracker/O2.Tracker.DbUtility/O2.Tracker.DbUtility.csproj b/src/Services/on-tracker/O2.Tracker.DbUtility/O2.Tracker.DbUtility.csproj new file mode 100644 index 00000000..af96d7e6 --- /dev/null +++ b/src/Services/on-tracker/O2.Tracker.DbUtility/O2.Tracker.DbUtility.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + + + + + + + + + Always + + + + diff --git a/src/Services/on-tracker/O2.Tracker.DbUtility/Point.cs b/src/Services/on-tracker/O2.Tracker.DbUtility/Point.cs new file mode 100644 index 00000000..cb96e6d1 --- /dev/null +++ b/src/Services/on-tracker/O2.Tracker.DbUtility/Point.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace O2.Tracker.DbUtility +{ + [DataContract] + public sealed class Point + { + [DataMember] + public double lat { get; set; } + + [DataMember] + public double lon { get; set; } + } +} \ No newline at end of file